blob: ef2554da31745de5b99ab629cca196b72e21c42f [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
15import os
16import optparse
Conley Owensd21720d2012-04-16 11:02:21 -070017import platform
Colin Cross5acde752012-03-28 20:15:45 -070018import re
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070019import sys
20
David Rileye0684ad2017-04-05 00:02:59 -070021from event_log import EventLog
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070022from error import NoSuchProjectError
Colin Cross5acde752012-03-28 20:15:45 -070023from error import InvalidProjectGroupsError
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070024
David Pursehouseb148ac92012-11-16 09:33:39 +090025
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070026class Command(object):
27 """Base class for any command line action in repo.
28 """
29
30 common = False
David Rileye0684ad2017-04-05 00:02:59 -070031 event_log = EventLog()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070032 manifest = None
33 _optparse = None
34
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -070035 def WantPager(self, _opt):
Shawn O. Pearcedb45da12009-04-18 13:49:13 -070036 return False
37
David Pursehouseb148ac92012-11-16 09:33:39 +090038 def ReadEnvironmentOptions(self, opts):
39 """ Set options from environment variables. """
40
41 env_options = self._RegisteredEnvironmentOptions()
42
43 for env_key, opt_key in env_options.items():
44 # Get the user-set option value if any
45 opt_value = getattr(opts, opt_key)
46
47 # If the value is set, it means the user has passed it as a command
48 # line option, and we should use that. Otherwise we can try to set it
49 # with the value from the corresponding environment variable.
50 if opt_value is not None:
51 continue
52
53 env_value = os.environ.get(env_key)
54 if env_value is not None:
55 setattr(opts, opt_key, env_value)
56
57 return opts
58
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070059 @property
60 def OptionParser(self):
61 if self._optparse is None:
62 try:
63 me = 'repo %s' % self.NAME
64 usage = self.helpUsage.strip().replace('%prog', me)
65 except AttributeError:
66 usage = 'repo %s' % self.NAME
Mike Frysinger72ebf192020-02-19 01:20:18 -050067 epilog = 'Run `repo help %s` to view the detailed manual.' % self.NAME
68 self._optparse = optparse.OptionParser(usage=usage, epilog=epilog)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070069 self._Options(self._optparse)
70 return self._optparse
71
72 def _Options(self, p):
73 """Initialize the option parser.
74 """
75
David Pursehouseb148ac92012-11-16 09:33:39 +090076 def _RegisteredEnvironmentOptions(self):
77 """Get options that can be set from environment variables.
78
79 Return a dictionary mapping environment variable name
80 to option key name that it can override.
81
82 Example: {'REPO_MY_OPTION': 'my_option'}
83
84 Will allow the option with key value 'my_option' to be set
85 from the value in the environment variable named 'REPO_MY_OPTION'.
86
87 Note: This does not work properly for options that are explicitly
88 set to None by the user, or options that are defined with a
89 default value other than None.
90
91 """
92 return {}
93
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070094 def Usage(self):
95 """Display usage and terminate.
96 """
97 self.OptionParser.print_usage()
98 sys.exit(1)
99
Mike Frysingerae6cb082019-08-27 01:10:59 -0400100 def ValidateOptions(self, opt, args):
101 """Validate the user options & arguments before executing.
102
103 This is meant to help break the code up into logical steps. Some tips:
104 * Use self.OptionParser.error to display CLI related errors.
105 * Adjust opt member defaults as makes sense.
106 * Adjust the args list, but do so inplace so the caller sees updates.
107 * Try to avoid updating self state. Leave that to Execute.
108 """
109
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700110 def Execute(self, opt, args):
111 """Perform the action, after option parsing is complete.
112 """
113 raise NotImplementedError
Conley Owens971de8e2012-04-16 10:36:08 -0700114
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800115 def _ResetPathToProjectMap(self, projects):
116 self._by_path = dict((p.worktree, p) for p in projects)
117
118 def _UpdatePathToProjectMap(self, project):
119 self._by_path[project.worktree] = project
120
Simran Basib9a1b732015-08-20 12:19:28 -0700121 def _GetProjectByPath(self, manifest, path):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800122 project = None
123 if os.path.exists(path):
124 oldpath = None
David Pursehouse5a2517f2020-02-12 14:55:01 +0900125 while (path and
126 path != oldpath and
127 path != manifest.topdir):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800128 try:
129 project = self._by_path[path]
130 break
131 except KeyError:
132 oldpath = path
133 path = os.path.dirname(path)
Mark E. Hamiltonf9fe3e12016-02-23 18:10:42 -0700134 if not project and path == manifest.topdir:
135 try:
136 project = self._by_path[path]
137 except KeyError:
138 pass
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800139 else:
140 try:
141 project = self._by_path[path]
142 except KeyError:
143 pass
144 return project
145
Simran Basib9a1b732015-08-20 12:19:28 -0700146 def GetProjects(self, args, manifest=None, groups='', missing_ok=False,
147 submodules_ok=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700148 """A list of projects that match the arguments.
149 """
Simran Basib9a1b732015-08-20 12:19:28 -0700150 if not manifest:
151 manifest = self.manifest
152 all_projects_list = manifest.projects
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700153 result = []
154
Simran Basib9a1b732015-08-20 12:19:28 -0700155 mp = manifest.manifestProject
Colin Cross5acde752012-03-28 20:15:45 -0700156
Graham Christensen0369a062015-07-29 17:02:54 -0500157 if not groups:
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700158 groups = mp.config.GetString('manifest.groups')
Colin Crossc39864f2012-04-23 13:41:58 -0700159 if not groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500160 groups = 'default,platform-' + platform.system().lower()
David Pursehouse1d947b32012-10-25 12:23:11 +0900161 groups = [x for x in re.split(r'[,\s]+', groups) if x]
Colin Cross5acde752012-03-28 20:15:45 -0700162
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700163 if not args:
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800164 derived_projects = {}
165 for project in all_projects_list:
166 if submodules_ok or project.sync_s:
167 derived_projects.update((p.name, p)
168 for p in project.GetDerivedSubprojects())
169 all_projects_list.extend(derived_projects.values())
170 for project in all_projects_list:
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700171 if (missing_ok or project.Exists) and project.MatchesGroups(groups):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700172 result.append(project)
173 else:
David James8d201162013-10-11 17:03:19 -0700174 self._ResetPathToProjectMap(all_projects_list)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700175
176 for arg in args:
Mike Frysingere778e572019-10-04 14:21:41 -0400177 # We have to filter by manifest groups in case the requested project is
178 # checked out multiple times or differently based on them.
179 projects = [project for project in manifest.GetProjectsWithName(arg)
180 if project.MatchesGroups(groups)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700181
David James8d201162013-10-11 17:03:19 -0700182 if not projects:
Anthony Newnamdf14a702011-01-09 17:31:57 -0800183 path = os.path.abspath(arg).replace('\\', '/')
Simran Basib9a1b732015-08-20 12:19:28 -0700184 project = self._GetProjectByPath(manifest, path)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700185
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800186 # If it's not a derived project, update path->project mapping and
187 # search again, as arg might actually point to a derived subproject.
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700188 if (project and not project.Derived and (submodules_ok or
189 project.sync_s)):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800190 search_again = False
191 for subproject in project.GetDerivedSubprojects():
192 self._UpdatePathToProjectMap(subproject)
193 search_again = True
194 if search_again:
Simran Basib9a1b732015-08-20 12:19:28 -0700195 project = self._GetProjectByPath(manifest, path) or project
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700196
David James8d201162013-10-11 17:03:19 -0700197 if project:
198 projects = [project]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700199
David James8d201162013-10-11 17:03:19 -0700200 if not projects:
201 raise NoSuchProjectError(arg)
202
203 for project in projects:
204 if not missing_ok and not project.Exists:
Mike Frysingere778e572019-10-04 14:21:41 -0400205 raise NoSuchProjectError('%s (%s)' % (arg, project.relpath))
David James8d201162013-10-11 17:03:19 -0700206 if not project.MatchesGroups(groups):
207 raise InvalidProjectGroupsError(arg)
208
209 result.extend(projects)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700210
211 def _getpath(x):
212 return x.relpath
213 result.sort(key=_getpath)
214 return result
215
Takeshi Kanemoto1f056442016-01-26 14:11:35 +0900216 def FindProjects(self, args, inverse=False):
Zhiguang Lia8864fb2013-03-15 10:32:10 +0800217 result = []
David Pursehouse84c4d3c2013-04-30 10:57:37 +0900218 patterns = [re.compile(r'%s' % a, re.IGNORECASE) for a in args]
Zhiguang Lia8864fb2013-03-15 10:32:10 +0800219 for project in self.GetProjects(''):
David Pursehouse84c4d3c2013-04-30 10:57:37 +0900220 for pattern in patterns:
Takeshi Kanemoto1f056442016-01-26 14:11:35 +0900221 match = pattern.search(project.name) or pattern.search(project.relpath)
222 if not inverse and match:
Zhiguang Lia8864fb2013-03-15 10:32:10 +0800223 result.append(project)
224 break
Takeshi Kanemoto1f056442016-01-26 14:11:35 +0900225 if inverse and match:
226 break
227 else:
228 if inverse:
229 result.append(project)
Zhiguang Lia8864fb2013-03-15 10:32:10 +0800230 result.sort(key=lambda project: project.relpath)
231 return result
232
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700233
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700234class InteractiveCommand(Command):
235 """Command which requires user interaction on the tty and
236 must not run within a pager, even if the user asks to.
237 """
David Pursehouse819827a2020-02-12 15:20:19 +0900238
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700239 def WantPager(self, _opt):
Shawn O. Pearcedb45da12009-04-18 13:49:13 -0700240 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700241
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700242
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700243class PagedCommand(Command):
244 """Command which defaults to output in a pager, as its
245 display tends to be larger than one screen full.
246 """
David Pursehouse819827a2020-02-12 15:20:19 +0900247
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700248 def WantPager(self, _opt):
Shawn O. Pearcedb45da12009-04-18 13:49:13 -0700249 return True
Shawn O. Pearcec95583b2009-03-03 17:47:06 -0800250
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700251
Shawn O. Pearcec95583b2009-03-03 17:47:06 -0800252class MirrorSafeCommand(object):
253 """Command permits itself to run within a mirror,
254 and does not require a working directory.
255 """
Dan Willemsen9ff2ece2015-08-31 15:45:06 -0700256
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700257
Dan Willemsen79360642015-08-31 15:45:06 -0700258class GitcAvailableCommand(object):
Dan Willemsen9ff2ece2015-08-31 15:45:06 -0700259 """Command that requires GITC to be available, but does
260 not require the local client to be a GITC client.
261 """
Dan Willemsen79360642015-08-31 15:45:06 -0700262
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700263
Dan Willemsen79360642015-08-31 15:45:06 -0700264class GitcClientCommand(object):
265 """Command that requires the local client to be a GITC
266 client.
267 """