blob: 2a2ce1386244ac36be80f246854b348e7dfda14d [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Kuang-che Wu39ffd992024-10-18 23:32:08 +080015import contextlib
Mike Frysingerb5d075d2021-03-01 00:56:38 -050016import multiprocessing
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070017import optparse
Mike Frysinger64477332023-08-21 21:20:32 -040018import os
Colin Cross5acde752012-03-28 20:15:45 -070019import re
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070020
Colin Cross5acde752012-03-28 20:15:45 -070021from error import InvalidProjectGroupsError
Mike Frysinger64477332023-08-21 21:20:32 -040022from error import NoSuchProjectError
Jason Changf9aacd42023-08-03 14:38:00 -070023from error import RepoExitError
Mike Frysinger64477332023-08-21 21:20:32 -040024from event_log import EventLog
Mike Frysingerb5d075d2021-03-01 00:56:38 -050025import progress
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070026
David Pursehouseb148ac92012-11-16 09:33:39 +090027
Mike Frysingerdf8b1cb2021-07-26 15:59:20 -040028# Are we generating man-pages?
Gavin Makea2e3302023-03-11 06:46:20 +000029GENERATE_MANPAGES = os.environ.get("_REPO_GENERATE_MANPAGES_") == " indeed! "
Mike Frysingerdf8b1cb2021-07-26 15:59:20 -040030
31
Mike Frysinger7c871162021-02-16 01:45:39 -050032# Number of projects to submit to a single worker process at a time.
33# This number represents a tradeoff between the overhead of IPC and finer
34# grained opportunity for parallelism. This particular value was chosen by
35# iterating through powers of two until the overall performance no longer
36# improved. The performance of this batch size is not a function of the
37# number of cores on the system.
38WORKER_BATCH_SIZE = 32
39
40
Mike Frysinger6a2400a2021-02-16 01:43:31 -050041# How many jobs to run in parallel by default? This assumes the jobs are
42# largely I/O bound and do not hit the network.
43DEFAULT_LOCAL_JOBS = min(os.cpu_count(), 8)
44
45
Jason Changf9aacd42023-08-03 14:38:00 -070046class UsageError(RepoExitError):
47 """Exception thrown with invalid command usage."""
48
49
Mike Frysingerd4aee652023-10-19 05:13:32 -040050class Command:
Gavin Makea2e3302023-03-11 06:46:20 +000051 """Base class for any command line action in repo."""
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070052
Gavin Makea2e3302023-03-11 06:46:20 +000053 # Singleton for all commands to track overall repo command execution and
54 # provide event summary to callers. Only used by sync subcommand currently.
55 #
56 # NB: This is being replaced by git trace2 events. See git_trace2_event_log.
57 event_log = EventLog()
Mike Frysingerd88b3692021-06-14 16:09:29 -040058
Gavin Makea2e3302023-03-11 06:46:20 +000059 # Whether this command is a "common" one, i.e. whether the user would
60 # commonly use it or it's a more uncommon command. This is used by the help
61 # command to show short-vs-full summaries.
62 COMMON = False
Mike Frysinger4f210542021-06-14 16:05:19 -040063
Gavin Makea2e3302023-03-11 06:46:20 +000064 # Whether this command supports running in parallel. If greater than 0,
65 # it is the number of parallel jobs to default to.
66 PARALLEL_JOBS = None
Mike Frysinger6a2400a2021-02-16 01:43:31 -050067
Gavin Makea2e3302023-03-11 06:46:20 +000068 # Whether this command supports Multi-manifest. If False, then main.py will
69 # iterate over the manifests and invoke the command once per (sub)manifest.
70 # This is only checked after calling ValidateOptions, so that partially
71 # migrated subcommands can set it to False.
72 MULTI_MANIFEST_SUPPORT = True
LaMont Jonescc879a92021-11-18 22:40:18 +000073
Kuang-che Wu39ffd992024-10-18 23:32:08 +080074 # Shared data across parallel execution workers.
75 _parallel_context = None
76
77 @classmethod
78 def get_parallel_context(cls):
79 assert cls._parallel_context is not None
80 return cls._parallel_context
81
Gavin Makea2e3302023-03-11 06:46:20 +000082 def __init__(
83 self,
84 repodir=None,
85 client=None,
86 manifest=None,
Gavin Makea2e3302023-03-11 06:46:20 +000087 git_event_log=None,
88 outer_client=None,
89 outer_manifest=None,
90 ):
91 self.repodir = repodir
92 self.client = client
93 self.outer_client = outer_client or client
94 self.manifest = manifest
Gavin Makea2e3302023-03-11 06:46:20 +000095 self.git_event_log = git_event_log
96 self.outer_manifest = outer_manifest
Mike Frysingerd58d0dd2021-06-14 16:17:27 -040097
Gavin Makea2e3302023-03-11 06:46:20 +000098 # Cache for the OptionParser property.
99 self._optparse = None
Mike Frysingerd58d0dd2021-06-14 16:17:27 -0400100
Gavin Makea2e3302023-03-11 06:46:20 +0000101 def WantPager(self, _opt):
102 return False
Shawn O. Pearcedb45da12009-04-18 13:49:13 -0700103
Gavin Makea2e3302023-03-11 06:46:20 +0000104 def ReadEnvironmentOptions(self, opts):
105 """Set options from environment variables."""
David Pursehouseb148ac92012-11-16 09:33:39 +0900106
Gavin Makea2e3302023-03-11 06:46:20 +0000107 env_options = self._RegisteredEnvironmentOptions()
David Pursehouseb148ac92012-11-16 09:33:39 +0900108
Gavin Makea2e3302023-03-11 06:46:20 +0000109 for env_key, opt_key in env_options.items():
110 # Get the user-set option value if any
111 opt_value = getattr(opts, opt_key)
David Pursehouseb148ac92012-11-16 09:33:39 +0900112
Gavin Makea2e3302023-03-11 06:46:20 +0000113 # If the value is set, it means the user has passed it as a command
114 # line option, and we should use that. Otherwise we can try to set
115 # it with the value from the corresponding environment variable.
116 if opt_value is not None:
117 continue
David Pursehouseb148ac92012-11-16 09:33:39 +0900118
Gavin Makea2e3302023-03-11 06:46:20 +0000119 env_value = os.environ.get(env_key)
120 if env_value is not None:
121 setattr(opts, opt_key, env_value)
David Pursehouseb148ac92012-11-16 09:33:39 +0900122
Gavin Makea2e3302023-03-11 06:46:20 +0000123 return opts
David Pursehouseb148ac92012-11-16 09:33:39 +0900124
Gavin Makea2e3302023-03-11 06:46:20 +0000125 @property
126 def OptionParser(self):
127 if self._optparse is None:
128 try:
129 me = "repo %s" % self.NAME
130 usage = self.helpUsage.strip().replace("%prog", me)
131 except AttributeError:
132 usage = "repo %s" % self.NAME
133 epilog = (
134 "Run `repo help %s` to view the detailed manual." % self.NAME
135 )
136 self._optparse = optparse.OptionParser(usage=usage, epilog=epilog)
137 self._CommonOptions(self._optparse)
138 self._Options(self._optparse)
139 return self._optparse
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700140
Gavin Makea2e3302023-03-11 06:46:20 +0000141 def _CommonOptions(self, p, opt_v=True):
142 """Initialize the option parser with common options.
Mike Frysinger9180a072021-04-13 14:57:40 -0400143
Gavin Makea2e3302023-03-11 06:46:20 +0000144 These will show up for *all* subcommands, so use sparingly.
145 NB: Keep in sync with repo:InitParser().
146 """
147 g = p.add_option_group("Logging options")
148 opts = ["-v"] if opt_v else []
149 g.add_option(
150 *opts,
151 "--verbose",
152 dest="output_mode",
153 action="store_true",
154 help="show all output",
155 )
156 g.add_option(
157 "-q",
158 "--quiet",
159 dest="output_mode",
160 action="store_false",
161 help="only show errors",
162 )
Mike Frysinger9180a072021-04-13 14:57:40 -0400163
Gavin Makea2e3302023-03-11 06:46:20 +0000164 if self.PARALLEL_JOBS is not None:
165 default = "based on number of CPU cores"
166 if not GENERATE_MANPAGES:
167 # Only include active cpu count if we aren't generating man
168 # pages.
169 default = f"%default; {default}"
170 p.add_option(
171 "-j",
172 "--jobs",
173 type=int,
174 default=self.PARALLEL_JOBS,
175 help=f"number of jobs to run in parallel (default: {default})",
176 )
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700177
Gavin Makea2e3302023-03-11 06:46:20 +0000178 m = p.add_option_group("Multi-manifest options")
179 m.add_option(
180 "--outer-manifest",
181 action="store_true",
182 default=None,
183 help="operate starting at the outermost manifest",
184 )
185 m.add_option(
186 "--no-outer-manifest",
187 dest="outer_manifest",
188 action="store_false",
189 help="do not operate on outer manifests",
190 )
191 m.add_option(
192 "--this-manifest-only",
193 action="store_true",
194 default=None,
195 help="only operate on this (sub)manifest",
196 )
197 m.add_option(
198 "--no-this-manifest-only",
199 "--all-manifests",
200 dest="this_manifest_only",
201 action="store_false",
202 help="operate on this manifest and its submanifests",
203 )
LaMont Jonescc879a92021-11-18 22:40:18 +0000204
Gavin Makea2e3302023-03-11 06:46:20 +0000205 def _Options(self, p):
206 """Initialize the option parser with subcommand-specific options."""
Mike Frysinger9180a072021-04-13 14:57:40 -0400207
Gavin Makea2e3302023-03-11 06:46:20 +0000208 def _RegisteredEnvironmentOptions(self):
209 """Get options that can be set from environment variables.
David Pursehouseb148ac92012-11-16 09:33:39 +0900210
Gavin Makea2e3302023-03-11 06:46:20 +0000211 Return a dictionary mapping environment variable name
212 to option key name that it can override.
David Pursehouseb148ac92012-11-16 09:33:39 +0900213
Gavin Makea2e3302023-03-11 06:46:20 +0000214 Example: {'REPO_MY_OPTION': 'my_option'}
David Pursehouseb148ac92012-11-16 09:33:39 +0900215
Gavin Makea2e3302023-03-11 06:46:20 +0000216 Will allow the option with key value 'my_option' to be set
217 from the value in the environment variable named 'REPO_MY_OPTION'.
David Pursehouseb148ac92012-11-16 09:33:39 +0900218
Gavin Makea2e3302023-03-11 06:46:20 +0000219 Note: This does not work properly for options that are explicitly
220 set to None by the user, or options that are defined with a
221 default value other than None.
David Pursehouseb148ac92012-11-16 09:33:39 +0900222
Gavin Makea2e3302023-03-11 06:46:20 +0000223 """
224 return {}
David Pursehouseb148ac92012-11-16 09:33:39 +0900225
Gavin Makea2e3302023-03-11 06:46:20 +0000226 def Usage(self):
227 """Display usage and terminate."""
228 self.OptionParser.print_usage()
Jason Changf9aacd42023-08-03 14:38:00 -0700229 raise UsageError()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700230
Gavin Makea2e3302023-03-11 06:46:20 +0000231 def CommonValidateOptions(self, opt, args):
232 """Validate common options."""
233 opt.quiet = opt.output_mode is False
234 opt.verbose = opt.output_mode is True
235 if opt.outer_manifest is None:
236 # By default, treat multi-manifest instances as a single manifest
237 # from the user's perspective.
238 opt.outer_manifest = True
Mike Frysinger9180a072021-04-13 14:57:40 -0400239
Gavin Makea2e3302023-03-11 06:46:20 +0000240 def ValidateOptions(self, opt, args):
241 """Validate the user options & arguments before executing.
Mike Frysingerae6cb082019-08-27 01:10:59 -0400242
Gavin Makea2e3302023-03-11 06:46:20 +0000243 This is meant to help break the code up into logical steps. Some tips:
244 * Use self.OptionParser.error to display CLI related errors.
245 * Adjust opt member defaults as makes sense.
246 * Adjust the args list, but do so inplace so the caller sees updates.
247 * Try to avoid updating self state. Leave that to Execute.
248 """
Mike Frysingerae6cb082019-08-27 01:10:59 -0400249
Gavin Makea2e3302023-03-11 06:46:20 +0000250 def Execute(self, opt, args):
251 """Perform the action, after option parsing is complete."""
252 raise NotImplementedError
Conley Owens971de8e2012-04-16 10:36:08 -0700253
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800254 @classmethod
255 @contextlib.contextmanager
256 def ParallelContext(cls):
257 """Obtains the context, which is shared to ExecuteInParallel workers.
258
259 Callers can store data in the context dict before invocation of
260 ExecuteInParallel. The dict will then be shared to child workers of
261 ExecuteInParallel.
262 """
263 assert cls._parallel_context is None
264 cls._parallel_context = {}
265 try:
266 yield
267 finally:
268 cls._parallel_context = None
269
270 @classmethod
271 def _SetParallelContext(cls, context):
272 cls._parallel_context = context
273
274 @classmethod
Gavin Makea2e3302023-03-11 06:46:20 +0000275 def ExecuteInParallel(
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800276 cls,
277 jobs,
278 func,
279 inputs,
280 callback,
281 output=None,
282 ordered=False,
283 chunksize=WORKER_BATCH_SIZE,
Gavin Makea2e3302023-03-11 06:46:20 +0000284 ):
285 """Helper for managing parallel execution boiler plate.
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500286
Gavin Makea2e3302023-03-11 06:46:20 +0000287 For subcommands that can easily split their work up.
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500288
Gavin Makea2e3302023-03-11 06:46:20 +0000289 Args:
290 jobs: How many parallel processes to use.
291 func: The function to apply to each of the |inputs|. Usually a
292 functools.partial for wrapping additional arguments. It will be
293 run in a separate process, so it must be pickalable, so nested
294 functions won't work. Methods on the subcommand Command class
295 should work.
296 inputs: The list of items to process. Must be a list.
297 callback: The function to pass the results to for processing. It
298 will be executed in the main thread and process the results of
299 |func| as they become available. Thus it may be a local nested
300 function. Its return value is passed back directly. It takes
301 three arguments:
302 - The processing pool (or None with one job).
303 - The |output| argument.
304 - An iterator for the results.
305 output: An output manager. May be progress.Progess or
306 color.Coloring.
307 ordered: Whether the jobs should be processed in order.
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800308 chunksize: The number of jobs processed in batch by parallel
309 workers.
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500310
Gavin Makea2e3302023-03-11 06:46:20 +0000311 Returns:
312 The |callback| function's results are returned.
313 """
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800314 try:
Gavin Makea2e3302023-03-11 06:46:20 +0000315 # NB: Multiprocessing is heavy, so don't spin it up for one job.
316 if len(inputs) == 1 or jobs == 1:
317 return callback(None, output, (func(x) for x in inputs))
318 else:
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800319 with multiprocessing.Pool(
320 jobs,
321 initializer=cls._SetParallelContext,
322 initargs=(cls._parallel_context,),
323 ) as pool:
Gavin Makea2e3302023-03-11 06:46:20 +0000324 submit = pool.imap if ordered else pool.imap_unordered
325 return callback(
326 pool,
327 output,
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800328 submit(func, inputs, chunksize=chunksize),
Gavin Makea2e3302023-03-11 06:46:20 +0000329 )
330 finally:
331 if isinstance(output, progress.Progress):
332 output.end()
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800333
Gavin Makea2e3302023-03-11 06:46:20 +0000334 def _ResetPathToProjectMap(self, projects):
Jason R. Coombs0bcffd82023-10-20 23:29:42 +0545335 self._by_path = {p.worktree: p for p in projects}
LaMont Jonesff6b1da2022-06-01 21:03:34 +0000336
Gavin Makea2e3302023-03-11 06:46:20 +0000337 def _UpdatePathToProjectMap(self, project):
338 self._by_path[project.worktree] = project
LaMont Jonesff6b1da2022-06-01 21:03:34 +0000339
Gavin Makea2e3302023-03-11 06:46:20 +0000340 def _GetProjectByPath(self, manifest, path):
341 project = None
342 if os.path.exists(path):
343 oldpath = None
344 while path and path != oldpath and path != manifest.topdir:
345 try:
346 project = self._by_path[path]
347 break
348 except KeyError:
349 oldpath = path
350 path = os.path.dirname(path)
351 if not project and path == manifest.topdir:
352 try:
353 project = self._by_path[path]
354 except KeyError:
355 pass
356 else:
357 try:
358 project = self._by_path[path]
359 except KeyError:
360 pass
361 return project
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700362
Gavin Makea2e3302023-03-11 06:46:20 +0000363 def GetProjects(
364 self,
365 args,
366 manifest=None,
367 groups="",
368 missing_ok=False,
369 submodules_ok=False,
370 all_manifests=False,
371 ):
372 """A list of projects that match the arguments.
Colin Cross5acde752012-03-28 20:15:45 -0700373
Gavin Makea2e3302023-03-11 06:46:20 +0000374 Args:
375 args: a list of (case-insensitive) strings, projects to search for.
376 manifest: an XmlManifest, the manifest to use, or None for default.
377 groups: a string, the manifest groups in use.
378 missing_ok: a boolean, whether to allow missing projects.
379 submodules_ok: a boolean, whether to allow submodules.
380 all_manifests: a boolean, if True then all manifests and
381 submanifests are used. If False, then only the local
382 (sub)manifest is used.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700383
Gavin Makea2e3302023-03-11 06:46:20 +0000384 Returns:
385 A list of matching Project instances.
386 """
387 if all_manifests:
388 if not manifest:
389 manifest = self.manifest.outer_client
390 all_projects_list = manifest.all_projects
391 else:
392 if not manifest:
393 manifest = self.manifest
394 all_projects_list = manifest.projects
395 result = []
396
397 if not groups:
398 groups = manifest.GetGroupsStr()
399 groups = [x for x in re.split(r"[,\s]+", groups) if x]
400
401 if not args:
402 derived_projects = {}
403 for project in all_projects_list:
404 if submodules_ok or project.sync_s:
405 derived_projects.update(
406 (p.name, p) for p in project.GetDerivedSubprojects()
407 )
408 all_projects_list.extend(derived_projects.values())
409 for project in all_projects_list:
410 if (missing_ok or project.Exists) and project.MatchesGroups(
411 groups
412 ):
413 result.append(project)
414 else:
415 self._ResetPathToProjectMap(all_projects_list)
416
417 for arg in args:
418 # We have to filter by manifest groups in case the requested
419 # project is checked out multiple times or differently based on
420 # them.
421 projects = [
422 project
Sergiy Belozorov78e82ec2023-01-05 18:57:31 +0100423 for project in manifest.GetProjectsWithName(
Gavin Makea2e3302023-03-11 06:46:20 +0000424 arg, all_manifests=all_manifests
425 )
426 if project.MatchesGroups(groups)
427 ]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700428
Gavin Makea2e3302023-03-11 06:46:20 +0000429 if not projects:
430 path = os.path.abspath(arg).replace("\\", "/")
431 tree = manifest
432 if all_manifests:
433 # Look for the deepest matching submanifest.
434 for tree in reversed(list(manifest.all_manifests)):
435 if path.startswith(tree.topdir):
436 break
437 project = self._GetProjectByPath(tree, path)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700438
Gavin Makea2e3302023-03-11 06:46:20 +0000439 # If it's not a derived project, update path->project
440 # mapping and search again, as arg might actually point to
441 # a derived subproject.
442 if (
443 project
444 and not project.Derived
445 and (submodules_ok or project.sync_s)
446 ):
447 search_again = False
448 for subproject in project.GetDerivedSubprojects():
449 self._UpdatePathToProjectMap(subproject)
450 search_again = True
451 if search_again:
452 project = (
453 self._GetProjectByPath(manifest, path)
454 or project
455 )
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700456
Gavin Makea2e3302023-03-11 06:46:20 +0000457 if project:
458 projects = [project]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700459
Gavin Makea2e3302023-03-11 06:46:20 +0000460 if not projects:
461 raise NoSuchProjectError(arg)
David James8d201162013-10-11 17:03:19 -0700462
Gavin Makea2e3302023-03-11 06:46:20 +0000463 for project in projects:
464 if not missing_ok and not project.Exists:
465 raise NoSuchProjectError(
466 "%s (%s)"
467 % (arg, project.RelPath(local=not all_manifests))
468 )
469 if not project.MatchesGroups(groups):
470 raise InvalidProjectGroupsError(arg)
David James8d201162013-10-11 17:03:19 -0700471
Gavin Makea2e3302023-03-11 06:46:20 +0000472 result.extend(projects)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700473
Gavin Makea2e3302023-03-11 06:46:20 +0000474 def _getpath(x):
475 return x.relpath
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700476
Gavin Makea2e3302023-03-11 06:46:20 +0000477 result.sort(key=_getpath)
478 return result
LaMont Jonescc879a92021-11-18 22:40:18 +0000479
Gavin Makea2e3302023-03-11 06:46:20 +0000480 def FindProjects(self, args, inverse=False, all_manifests=False):
481 """Find projects from command line arguments.
Zhiguang Lia8864fb2013-03-15 10:32:10 +0800482
Gavin Makea2e3302023-03-11 06:46:20 +0000483 Args:
484 args: a list of (case-insensitive) strings, projects to search for.
485 inverse: a boolean, if True, then projects not matching any |args|
486 are returned.
487 all_manifests: a boolean, if True then all manifests and
488 submanifests are used. If False, then only the local
489 (sub)manifest is used.
490 """
491 result = []
492 patterns = [re.compile(r"%s" % a, re.IGNORECASE) for a in args]
493 for project in self.GetProjects("", all_manifests=all_manifests):
494 paths = [project.name, project.RelPath(local=not all_manifests)]
495 for pattern in patterns:
496 match = any(pattern.search(x) for x in paths)
497 if not inverse and match:
498 result.append(project)
499 break
500 if inverse and match:
501 break
502 else:
503 if inverse:
504 result.append(project)
505 result.sort(
506 key=lambda project: (project.manifest.path_prefix, project.relpath)
507 )
508 return result
LaMont Jonescc879a92021-11-18 22:40:18 +0000509
Gavin Makea2e3302023-03-11 06:46:20 +0000510 def ManifestList(self, opt):
511 """Yields all of the manifests to traverse.
512
513 Args:
514 opt: The command options.
515 """
516 top = self.outer_manifest
517 if not opt.outer_manifest or opt.this_manifest_only:
518 top = self.manifest
519 yield top
520 if not opt.this_manifest_only:
Jason R. Coombs8dd85212023-10-20 06:48:20 -0400521 yield from top.all_children
LaMont Jonescc879a92021-11-18 22:40:18 +0000522
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700523
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700524class InteractiveCommand(Command):
Gavin Makea2e3302023-03-11 06:46:20 +0000525 """Command which requires user interaction on the tty and must not run
526 within a pager, even if the user asks to.
527 """
David Pursehouse819827a2020-02-12 15:20:19 +0900528
Gavin Makea2e3302023-03-11 06:46:20 +0000529 def WantPager(self, _opt):
530 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700531
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700532
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700533class PagedCommand(Command):
Gavin Makea2e3302023-03-11 06:46:20 +0000534 """Command which defaults to output in a pager, as its display tends to be
535 larger than one screen full.
536 """
David Pursehouse819827a2020-02-12 15:20:19 +0900537
Gavin Makea2e3302023-03-11 06:46:20 +0000538 def WantPager(self, _opt):
539 return True
Shawn O. Pearcec95583b2009-03-03 17:47:06 -0800540
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700541
Mike Frysingerd4aee652023-10-19 05:13:32 -0400542class MirrorSafeCommand:
Gavin Makea2e3302023-03-11 06:46:20 +0000543 """Command permits itself to run within a mirror, and does not require a
544 working directory.
545 """
Dan Willemsen9ff2ece2015-08-31 15:45:06 -0700546
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700547
Mike Frysingerd4aee652023-10-19 05:13:32 -0400548class GitcClientCommand:
Gavin Makea2e3302023-03-11 06:46:20 +0000549 """Command that requires the local client to be a GITC client."""