summaryrefslogtreecommitdiffstats
path: root/command.py
diff options
context:
space:
mode:
Diffstat (limited to 'command.py')
-rw-r--r--command.py831
1 files changed, 449 insertions, 382 deletions
diff --git a/command.py b/command.py
index 68f36f03..939a4630 100644
--- a/command.py
+++ b/command.py
@@ -25,7 +25,7 @@ import progress
25 25
26 26
27# Are we generating man-pages? 27# Are we generating man-pages?
28GENERATE_MANPAGES = os.environ.get('_REPO_GENERATE_MANPAGES_') == ' indeed! ' 28GENERATE_MANPAGES = os.environ.get("_REPO_GENERATE_MANPAGES_") == " indeed! "
29 29
30 30
31# Number of projects to submit to a single worker process at a time. 31# Number of projects to submit to a single worker process at a time.
@@ -43,403 +43,470 @@ DEFAULT_LOCAL_JOBS = min(os.cpu_count(), 8)
43 43
44 44
45class Command(object): 45class Command(object):
46 """Base class for any command line action in repo. 46 """Base class for any command line action in repo."""
47 """ 47
48 48 # Singleton for all commands to track overall repo command execution and
49 # Singleton for all commands to track overall repo command execution and 49 # provide event summary to callers. Only used by sync subcommand currently.
50 # provide event summary to callers. Only used by sync subcommand currently. 50 #
51 # 51 # NB: This is being replaced by git trace2 events. See git_trace2_event_log.
52 # NB: This is being replaced by git trace2 events. See git_trace2_event_log. 52 event_log = EventLog()
53 event_log = EventLog() 53
54 54 # Whether this command is a "common" one, i.e. whether the user would
55 # Whether this command is a "common" one, i.e. whether the user would commonly 55 # commonly use it or it's a more uncommon command. This is used by the help
56 # use it or it's a more uncommon command. This is used by the help command to 56 # command to show short-vs-full summaries.
57 # show short-vs-full summaries. 57 COMMON = False
58 COMMON = False 58
59 59 # Whether this command supports running in parallel. If greater than 0,
60 # Whether this command supports running in parallel. If greater than 0, 60 # it is the number of parallel jobs to default to.
61 # it is the number of parallel jobs to default to. 61 PARALLEL_JOBS = None
62 PARALLEL_JOBS = None 62
63 63 # Whether this command supports Multi-manifest. If False, then main.py will
64 # Whether this command supports Multi-manifest. If False, then main.py will 64 # iterate over the manifests and invoke the command once per (sub)manifest.
65 # iterate over the manifests and invoke the command once per (sub)manifest. 65 # This is only checked after calling ValidateOptions, so that partially
66 # This is only checked after calling ValidateOptions, so that partially 66 # migrated subcommands can set it to False.
67 # migrated subcommands can set it to False. 67 MULTI_MANIFEST_SUPPORT = True
68 MULTI_MANIFEST_SUPPORT = True 68
69 69 def __init__(
70 def __init__(self, repodir=None, client=None, manifest=None, gitc_manifest=None, 70 self,
71 git_event_log=None, outer_client=None, outer_manifest=None): 71 repodir=None,
72 self.repodir = repodir 72 client=None,
73 self.client = client 73 manifest=None,
74 self.outer_client = outer_client or client 74 gitc_manifest=None,
75 self.manifest = manifest 75 git_event_log=None,
76 self.gitc_manifest = gitc_manifest 76 outer_client=None,
77 self.git_event_log = git_event_log 77 outer_manifest=None,
78 self.outer_manifest = outer_manifest 78 ):
79 79 self.repodir = repodir
80 # Cache for the OptionParser property. 80 self.client = client
81 self._optparse = None 81 self.outer_client = outer_client or client
82 82 self.manifest = manifest
83 def WantPager(self, _opt): 83 self.gitc_manifest = gitc_manifest
84 return False 84 self.git_event_log = git_event_log
85 85 self.outer_manifest = outer_manifest
86 def ReadEnvironmentOptions(self, opts): 86
87 """ Set options from environment variables. """ 87 # Cache for the OptionParser property.
88 88 self._optparse = None
89 env_options = self._RegisteredEnvironmentOptions() 89
90 90 def WantPager(self, _opt):
91 for env_key, opt_key in env_options.items(): 91 return False
92 # Get the user-set option value if any 92
93 opt_value = getattr(opts, opt_key) 93 def ReadEnvironmentOptions(self, opts):
94 94 """Set options from environment variables."""
95 # If the value is set, it means the user has passed it as a command 95
96 # line option, and we should use that. Otherwise we can try to set it 96 env_options = self._RegisteredEnvironmentOptions()
97 # with the value from the corresponding environment variable. 97
98 if opt_value is not None: 98 for env_key, opt_key in env_options.items():
99 continue 99 # Get the user-set option value if any
100 100 opt_value = getattr(opts, opt_key)
101 env_value = os.environ.get(env_key) 101
102 if env_value is not None: 102 # If the value is set, it means the user has passed it as a command
103 setattr(opts, opt_key, env_value) 103 # line option, and we should use that. Otherwise we can try to set
104 104 # it with the value from the corresponding environment variable.
105 return opts 105 if opt_value is not None:
106 106 continue
107 @property 107
108 def OptionParser(self): 108 env_value = os.environ.get(env_key)
109 if self._optparse is None: 109 if env_value is not None:
110 try: 110 setattr(opts, opt_key, env_value)
111 me = 'repo %s' % self.NAME 111
112 usage = self.helpUsage.strip().replace('%prog', me) 112 return opts
113 except AttributeError: 113
114 usage = 'repo %s' % self.NAME 114 @property
115 epilog = 'Run `repo help %s` to view the detailed manual.' % self.NAME 115 def OptionParser(self):
116 self._optparse = optparse.OptionParser(usage=usage, epilog=epilog) 116 if self._optparse is None:
117 self._CommonOptions(self._optparse) 117 try:
118 self._Options(self._optparse) 118 me = "repo %s" % self.NAME
119 return self._optparse 119 usage = self.helpUsage.strip().replace("%prog", me)
120 120 except AttributeError:
121 def _CommonOptions(self, p, opt_v=True): 121 usage = "repo %s" % self.NAME
122 """Initialize the option parser with common options. 122 epilog = (
123 123 "Run `repo help %s` to view the detailed manual." % self.NAME
124 These will show up for *all* subcommands, so use sparingly. 124 )
125 NB: Keep in sync with repo:InitParser(). 125 self._optparse = optparse.OptionParser(usage=usage, epilog=epilog)
126 """ 126 self._CommonOptions(self._optparse)
127 g = p.add_option_group('Logging options') 127 self._Options(self._optparse)
128 opts = ['-v'] if opt_v else [] 128 return self._optparse
129 g.add_option(*opts, '--verbose', 129
130 dest='output_mode', action='store_true', 130 def _CommonOptions(self, p, opt_v=True):
131 help='show all output') 131 """Initialize the option parser with common options.
132 g.add_option('-q', '--quiet', 132
133 dest='output_mode', action='store_false', 133 These will show up for *all* subcommands, so use sparingly.
134 help='only show errors') 134 NB: Keep in sync with repo:InitParser().
135 135 """
136 if self.PARALLEL_JOBS is not None: 136 g = p.add_option_group("Logging options")
137 default = 'based on number of CPU cores' 137 opts = ["-v"] if opt_v else []
138 if not GENERATE_MANPAGES: 138 g.add_option(
139 # Only include active cpu count if we aren't generating man pages. 139 *opts,
140 default = f'%default; {default}' 140 "--verbose",
141 p.add_option( 141 dest="output_mode",
142 '-j', '--jobs', 142 action="store_true",
143 type=int, default=self.PARALLEL_JOBS, 143 help="show all output",
144 help=f'number of jobs to run in parallel (default: {default})') 144 )
145 145 g.add_option(
146 m = p.add_option_group('Multi-manifest options') 146 "-q",
147 m.add_option('--outer-manifest', action='store_true', default=None, 147 "--quiet",
148 help='operate starting at the outermost manifest') 148 dest="output_mode",
149 m.add_option('--no-outer-manifest', dest='outer_manifest', 149 action="store_false",
150 action='store_false', help='do not operate on outer manifests') 150 help="only show errors",
151 m.add_option('--this-manifest-only', action='store_true', default=None, 151 )
152 help='only operate on this (sub)manifest') 152
153 m.add_option('--no-this-manifest-only', '--all-manifests', 153 if self.PARALLEL_JOBS is not None:
154 dest='this_manifest_only', action='store_false', 154 default = "based on number of CPU cores"
155 help='operate on this manifest and its submanifests') 155 if not GENERATE_MANPAGES:
156 156 # Only include active cpu count if we aren't generating man
157 def _Options(self, p): 157 # pages.
158 """Initialize the option parser with subcommand-specific options.""" 158 default = f"%default; {default}"
159 159 p.add_option(
160 def _RegisteredEnvironmentOptions(self): 160 "-j",
161 """Get options that can be set from environment variables. 161 "--jobs",
162 162 type=int,
163 Return a dictionary mapping environment variable name 163 default=self.PARALLEL_JOBS,
164 to option key name that it can override. 164 help=f"number of jobs to run in parallel (default: {default})",
165 165 )
166 Example: {'REPO_MY_OPTION': 'my_option'} 166
167 167 m = p.add_option_group("Multi-manifest options")
168 Will allow the option with key value 'my_option' to be set 168 m.add_option(
169 from the value in the environment variable named 'REPO_MY_OPTION'. 169 "--outer-manifest",
170 170 action="store_true",
171 Note: This does not work properly for options that are explicitly 171 default=None,
172 set to None by the user, or options that are defined with a 172 help="operate starting at the outermost manifest",
173 default value other than None. 173 )
174 174 m.add_option(
175 """ 175 "--no-outer-manifest",
176 return {} 176 dest="outer_manifest",
177 177 action="store_false",
178 def Usage(self): 178 help="do not operate on outer manifests",
179 """Display usage and terminate. 179 )
180 """ 180 m.add_option(
181 self.OptionParser.print_usage() 181 "--this-manifest-only",
182 sys.exit(1) 182 action="store_true",
183 183 default=None,
184 def CommonValidateOptions(self, opt, args): 184 help="only operate on this (sub)manifest",
185 """Validate common options.""" 185 )
186 opt.quiet = opt.output_mode is False 186 m.add_option(
187 opt.verbose = opt.output_mode is True 187 "--no-this-manifest-only",
188 if opt.outer_manifest is None: 188 "--all-manifests",
189 # By default, treat multi-manifest instances as a single manifest from 189 dest="this_manifest_only",
190 # the user's perspective. 190 action="store_false",
191 opt.outer_manifest = True 191 help="operate on this manifest and its submanifests",
192 192 )
193 def ValidateOptions(self, opt, args): 193
194 """Validate the user options & arguments before executing. 194 def _Options(self, p):
195 195 """Initialize the option parser with subcommand-specific options."""
196 This is meant to help break the code up into logical steps. Some tips: 196
197 * Use self.OptionParser.error to display CLI related errors. 197 def _RegisteredEnvironmentOptions(self):
198 * Adjust opt member defaults as makes sense. 198 """Get options that can be set from environment variables.
199 * Adjust the args list, but do so inplace so the caller sees updates. 199
200 * Try to avoid updating self state. Leave that to Execute. 200 Return a dictionary mapping environment variable name
201 """ 201 to option key name that it can override.
202 202
203 def Execute(self, opt, args): 203 Example: {'REPO_MY_OPTION': 'my_option'}
204 """Perform the action, after option parsing is complete. 204
205 """ 205 Will allow the option with key value 'my_option' to be set
206 raise NotImplementedError 206 from the value in the environment variable named 'REPO_MY_OPTION'.
207 207
208 @staticmethod 208 Note: This does not work properly for options that are explicitly
209 def ExecuteInParallel(jobs, func, inputs, callback, output=None, ordered=False): 209 set to None by the user, or options that are defined with a
210 """Helper for managing parallel execution boiler plate. 210 default value other than None.
211 211
212 For subcommands that can easily split their work up. 212 """
213 213 return {}
214 Args: 214
215 jobs: How many parallel processes to use. 215 def Usage(self):
216 func: The function to apply to each of the |inputs|. Usually a 216 """Display usage and terminate."""
217 functools.partial for wrapping additional arguments. It will be run 217 self.OptionParser.print_usage()
218 in a separate process, so it must be pickalable, so nested functions 218 sys.exit(1)
219 won't work. Methods on the subcommand Command class should work. 219
220 inputs: The list of items to process. Must be a list. 220 def CommonValidateOptions(self, opt, args):
221 callback: The function to pass the results to for processing. It will be 221 """Validate common options."""
222 executed in the main thread and process the results of |func| as they 222 opt.quiet = opt.output_mode is False
223 become available. Thus it may be a local nested function. Its return 223 opt.verbose = opt.output_mode is True
224 value is passed back directly. It takes three arguments: 224 if opt.outer_manifest is None:
225 - The processing pool (or None with one job). 225 # By default, treat multi-manifest instances as a single manifest
226 - The |output| argument. 226 # from the user's perspective.
227 - An iterator for the results. 227 opt.outer_manifest = True
228 output: An output manager. May be progress.Progess or color.Coloring. 228
229 ordered: Whether the jobs should be processed in order. 229 def ValidateOptions(self, opt, args):
230 230 """Validate the user options & arguments before executing.
231 Returns: 231
232 The |callback| function's results are returned. 232 This is meant to help break the code up into logical steps. Some tips:
233 """ 233 * Use self.OptionParser.error to display CLI related errors.
234 try: 234 * Adjust opt member defaults as makes sense.
235 # NB: Multiprocessing is heavy, so don't spin it up for one job. 235 * Adjust the args list, but do so inplace so the caller sees updates.
236 if len(inputs) == 1 or jobs == 1: 236 * Try to avoid updating self state. Leave that to Execute.
237 return callback(None, output, (func(x) for x in inputs)) 237 """
238 else: 238
239 with multiprocessing.Pool(jobs) as pool: 239 def Execute(self, opt, args):
240 submit = pool.imap if ordered else pool.imap_unordered 240 """Perform the action, after option parsing is complete."""
241 return callback(pool, output, submit(func, inputs, chunksize=WORKER_BATCH_SIZE)) 241 raise NotImplementedError
242 finally: 242
243 if isinstance(output, progress.Progress): 243 @staticmethod
244 output.end() 244 def ExecuteInParallel(
245 245 jobs, func, inputs, callback, output=None, ordered=False
246 def _ResetPathToProjectMap(self, projects): 246 ):
247 self._by_path = dict((p.worktree, p) for p in projects) 247 """Helper for managing parallel execution boiler plate.
248 248
249 def _UpdatePathToProjectMap(self, project): 249 For subcommands that can easily split their work up.
250 self._by_path[project.worktree] = project 250
251 251 Args:
252 def _GetProjectByPath(self, manifest, path): 252 jobs: How many parallel processes to use.
253 project = None 253 func: The function to apply to each of the |inputs|. Usually a
254 if os.path.exists(path): 254 functools.partial for wrapping additional arguments. It will be
255 oldpath = None 255 run in a separate process, so it must be pickalable, so nested
256 while (path and 256 functions won't work. Methods on the subcommand Command class
257 path != oldpath and 257 should work.
258 path != manifest.topdir): 258 inputs: The list of items to process. Must be a list.
259 callback: The function to pass the results to for processing. It
260 will be executed in the main thread and process the results of
261 |func| as they become available. Thus it may be a local nested
262 function. Its return value is passed back directly. It takes
263 three arguments:
264 - The processing pool (or None with one job).
265 - The |output| argument.
266 - An iterator for the results.
267 output: An output manager. May be progress.Progess or
268 color.Coloring.
269 ordered: Whether the jobs should be processed in order.
270
271 Returns:
272 The |callback| function's results are returned.
273 """
259 try: 274 try:
260 project = self._by_path[path] 275 # NB: Multiprocessing is heavy, so don't spin it up for one job.
261 break 276 if len(inputs) == 1 or jobs == 1:
262 except KeyError: 277 return callback(None, output, (func(x) for x in inputs))
263 oldpath = path 278 else:
264 path = os.path.dirname(path) 279 with multiprocessing.Pool(jobs) as pool:
265 if not project and path == manifest.topdir: 280 submit = pool.imap if ordered else pool.imap_unordered
266 try: 281 return callback(
267 project = self._by_path[path] 282 pool,
268 except KeyError: 283 output,
269 pass 284 submit(func, inputs, chunksize=WORKER_BATCH_SIZE),
270 else: 285 )
271 try: 286 finally:
272 project = self._by_path[path] 287 if isinstance(output, progress.Progress):
273 except KeyError: 288 output.end()
274 pass 289
275 return project 290 def _ResetPathToProjectMap(self, projects):
276 291 self._by_path = dict((p.worktree, p) for p in projects)
277 def GetProjects(self, args, manifest=None, groups='', missing_ok=False, 292
278 submodules_ok=False, all_manifests=False): 293 def _UpdatePathToProjectMap(self, project):
279 """A list of projects that match the arguments. 294 self._by_path[project.worktree] = project
280 295
281 Args: 296 def _GetProjectByPath(self, manifest, path):
282 args: a list of (case-insensitive) strings, projects to search for. 297 project = None
283 manifest: an XmlManifest, the manifest to use, or None for default. 298 if os.path.exists(path):
284 groups: a string, the manifest groups in use. 299 oldpath = None
285 missing_ok: a boolean, whether to allow missing projects. 300 while path and path != oldpath and path != manifest.topdir:
286 submodules_ok: a boolean, whether to allow submodules. 301 try:
287 all_manifests: a boolean, if True then all manifests and submanifests are 302 project = self._by_path[path]
288 used. If False, then only the local (sub)manifest is used. 303 break
289 304 except KeyError:
290 Returns: 305 oldpath = path
291 A list of matching Project instances. 306 path = os.path.dirname(path)
292 """ 307 if not project and path == manifest.topdir:
293 if all_manifests: 308 try:
294 if not manifest: 309 project = self._by_path[path]
295 manifest = self.manifest.outer_client 310 except KeyError:
296 all_projects_list = manifest.all_projects 311 pass
297 else: 312 else:
298 if not manifest: 313 try:
299 manifest = self.manifest 314 project = self._by_path[path]
300 all_projects_list = manifest.projects 315 except KeyError:
301 result = [] 316 pass
302 317 return project
303 if not groups: 318
304 groups = manifest.GetGroupsStr() 319 def GetProjects(
305 groups = [x for x in re.split(r'[,\s]+', groups) if x] 320 self,
306 321 args,
307 if not args: 322 manifest=None,
308 derived_projects = {} 323 groups="",
309 for project in all_projects_list: 324 missing_ok=False,
310 if submodules_ok or project.sync_s: 325 submodules_ok=False,
311 derived_projects.update((p.name, p) 326 all_manifests=False,
312 for p in project.GetDerivedSubprojects()) 327 ):
313 all_projects_list.extend(derived_projects.values()) 328 """A list of projects that match the arguments.
314 for project in all_projects_list: 329
315 if (missing_ok or project.Exists) and project.MatchesGroups(groups): 330 Args:
316 result.append(project) 331 args: a list of (case-insensitive) strings, projects to search for.
317 else: 332 manifest: an XmlManifest, the manifest to use, or None for default.
318 self._ResetPathToProjectMap(all_projects_list) 333 groups: a string, the manifest groups in use.
319 334 missing_ok: a boolean, whether to allow missing projects.
320 for arg in args: 335 submodules_ok: a boolean, whether to allow submodules.
321 # We have to filter by manifest groups in case the requested project is 336 all_manifests: a boolean, if True then all manifests and
322 # checked out multiple times or differently based on them. 337 submanifests are used. If False, then only the local
323 projects = [project 338 (sub)manifest is used.
339
340 Returns:
341 A list of matching Project instances.
342 """
343 if all_manifests:
344 if not manifest:
345 manifest = self.manifest.outer_client
346 all_projects_list = manifest.all_projects
347 else:
348 if not manifest:
349 manifest = self.manifest
350 all_projects_list = manifest.projects
351 result = []
352
353 if not groups:
354 groups = manifest.GetGroupsStr()
355 groups = [x for x in re.split(r"[,\s]+", groups) if x]
356
357 if not args:
358 derived_projects = {}
359 for project in all_projects_list:
360 if submodules_ok or project.sync_s:
361 derived_projects.update(
362 (p.name, p) for p in project.GetDerivedSubprojects()
363 )
364 all_projects_list.extend(derived_projects.values())
365 for project in all_projects_list:
366 if (missing_ok or project.Exists) and project.MatchesGroups(
367 groups
368 ):
369 result.append(project)
370 else:
371 self._ResetPathToProjectMap(all_projects_list)
372
373 for arg in args:
374 # We have to filter by manifest groups in case the requested
375 # project is checked out multiple times or differently based on
376 # them.
377 projects = [
378 project
324 for project in manifest.GetProjectsWithName( 379 for project in manifest.GetProjectsWithName(
325 arg, all_manifests=all_manifests) 380 arg, all_manifests=all_manifests
326 if project.MatchesGroups(groups)] 381 )
327 382 if project.MatchesGroups(groups)
328 if not projects: 383 ]
329 path = os.path.abspath(arg).replace('\\', '/') 384
330 tree = manifest 385 if not projects:
331 if all_manifests: 386 path = os.path.abspath(arg).replace("\\", "/")
332 # Look for the deepest matching submanifest. 387 tree = manifest
333 for tree in reversed(list(manifest.all_manifests)): 388 if all_manifests:
334 if path.startswith(tree.topdir): 389 # Look for the deepest matching submanifest.
335 break 390 for tree in reversed(list(manifest.all_manifests)):
336 project = self._GetProjectByPath(tree, path) 391 if path.startswith(tree.topdir):
337 392 break
338 # If it's not a derived project, update path->project mapping and 393 project = self._GetProjectByPath(tree, path)
339 # search again, as arg might actually point to a derived subproject. 394
340 if (project and not project.Derived and (submodules_ok or 395 # If it's not a derived project, update path->project
341 project.sync_s)): 396 # mapping and search again, as arg might actually point to
342 search_again = False 397 # a derived subproject.
343 for subproject in project.GetDerivedSubprojects(): 398 if (
344 self._UpdatePathToProjectMap(subproject) 399 project
345 search_again = True 400 and not project.Derived
346 if search_again: 401 and (submodules_ok or project.sync_s)
347 project = self._GetProjectByPath(manifest, path) or project 402 ):
348 403 search_again = False
349 if project: 404 for subproject in project.GetDerivedSubprojects():
350 projects = [project] 405 self._UpdatePathToProjectMap(subproject)
351 406 search_again = True
352 if not projects: 407 if search_again:
353 raise NoSuchProjectError(arg) 408 project = (
354 409 self._GetProjectByPath(manifest, path)
355 for project in projects: 410 or project
356 if not missing_ok and not project.Exists: 411 )
357 raise NoSuchProjectError('%s (%s)' % ( 412
358 arg, project.RelPath(local=not all_manifests))) 413 if project:
359 if not project.MatchesGroups(groups): 414 projects = [project]
360 raise InvalidProjectGroupsError(arg) 415
361 416 if not projects:
362 result.extend(projects) 417 raise NoSuchProjectError(arg)
363 418
364 def _getpath(x): 419 for project in projects:
365 return x.relpath 420 if not missing_ok and not project.Exists:
366 result.sort(key=_getpath) 421 raise NoSuchProjectError(
367 return result 422 "%s (%s)"
368 423 % (arg, project.RelPath(local=not all_manifests))
369 def FindProjects(self, args, inverse=False, all_manifests=False): 424 )
370 """Find projects from command line arguments. 425 if not project.MatchesGroups(groups):
371 426 raise InvalidProjectGroupsError(arg)
372 Args: 427
373 args: a list of (case-insensitive) strings, projects to search for. 428 result.extend(projects)
374 inverse: a boolean, if True, then projects not matching any |args| are 429
375 returned. 430 def _getpath(x):
376 all_manifests: a boolean, if True then all manifests and submanifests are 431 return x.relpath
377 used. If False, then only the local (sub)manifest is used. 432
378 """ 433 result.sort(key=_getpath)
379 result = [] 434 return result
380 patterns = [re.compile(r'%s' % a, re.IGNORECASE) for a in args] 435
381 for project in self.GetProjects('', all_manifests=all_manifests): 436 def FindProjects(self, args, inverse=False, all_manifests=False):
382 paths = [project.name, project.RelPath(local=not all_manifests)] 437 """Find projects from command line arguments.
383 for pattern in patterns: 438
384 match = any(pattern.search(x) for x in paths) 439 Args:
385 if not inverse and match: 440 args: a list of (case-insensitive) strings, projects to search for.
386 result.append(project) 441 inverse: a boolean, if True, then projects not matching any |args|
387 break 442 are returned.
388 if inverse and match: 443 all_manifests: a boolean, if True then all manifests and
389 break 444 submanifests are used. If False, then only the local
390 else: 445 (sub)manifest is used.
391 if inverse: 446 """
392 result.append(project) 447 result = []
393 result.sort(key=lambda project: (project.manifest.path_prefix, 448 patterns = [re.compile(r"%s" % a, re.IGNORECASE) for a in args]
394 project.relpath)) 449 for project in self.GetProjects("", all_manifests=all_manifests):
395 return result 450 paths = [project.name, project.RelPath(local=not all_manifests)]
396 451 for pattern in patterns:
397 def ManifestList(self, opt): 452 match = any(pattern.search(x) for x in paths)
398 """Yields all of the manifests to traverse. 453 if not inverse and match:
399 454 result.append(project)
400 Args: 455 break
401 opt: The command options. 456 if inverse and match:
402 """ 457 break
403 top = self.outer_manifest 458 else:
404 if not opt.outer_manifest or opt.this_manifest_only: 459 if inverse:
405 top = self.manifest 460 result.append(project)
406 yield top 461 result.sort(
407 if not opt.this_manifest_only: 462 key=lambda project: (project.manifest.path_prefix, project.relpath)
408 for child in top.all_children: 463 )
409 yield child 464 return result
465
466 def ManifestList(self, opt):
467 """Yields all of the manifests to traverse.
468
469 Args:
470 opt: The command options.
471 """
472 top = self.outer_manifest
473 if not opt.outer_manifest or opt.this_manifest_only:
474 top = self.manifest
475 yield top
476 if not opt.this_manifest_only:
477 for child in top.all_children:
478 yield child
410 479
411 480
412class InteractiveCommand(Command): 481class InteractiveCommand(Command):
413 """Command which requires user interaction on the tty and 482 """Command which requires user interaction on the tty and must not run
414 must not run within a pager, even if the user asks to. 483 within a pager, even if the user asks to.
415 """ 484 """
416 485
417 def WantPager(self, _opt): 486 def WantPager(self, _opt):
418 return False 487 return False
419 488
420 489
421class PagedCommand(Command): 490class PagedCommand(Command):
422 """Command which defaults to output in a pager, as its 491 """Command which defaults to output in a pager, as its display tends to be
423 display tends to be larger than one screen full. 492 larger than one screen full.
424 """ 493 """
425 494
426 def WantPager(self, _opt): 495 def WantPager(self, _opt):
427 return True 496 return True
428 497
429 498
430class MirrorSafeCommand(object): 499class MirrorSafeCommand(object):
431 """Command permits itself to run within a mirror, 500 """Command permits itself to run within a mirror, and does not require a
432 and does not require a working directory. 501 working directory.
433 """ 502 """
434 503
435 504
436class GitcAvailableCommand(object): 505class GitcAvailableCommand(object):
437 """Command that requires GITC to be available, but does 506 """Command that requires GITC to be available, but does not require the
438 not require the local client to be a GITC client. 507 local client to be a GITC client.
439 """ 508 """
440 509
441 510
442class GitcClientCommand(object): 511class GitcClientCommand(object):
443 """Command that requires the local client to be a GITC 512 """Command that requires the local client to be a GITC client."""
444 client.
445 """