blob: 135c91fb4c76d3eb8b7f47630c9435328aaa0a84 [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
Raman Tenneti993af5e2021-05-12 12:00:31 -070015import collections
Colin Cross23acdd32012-04-21 00:33:54 -070016import itertools
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070017import os
Raman Tenneti080877e2021-03-09 15:19:06 -080018import platform
Conley Owensdb728cd2011-09-26 16:34:01 -070019import re
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070020import sys
David Pursehouse59bbb582013-05-17 10:49:33 +090021import xml.dom.minidom
Mike Frysingeracf63b22019-06-13 02:24:21 -040022import urllib.parse
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070023
Simran Basib9a1b732015-08-20 12:19:28 -070024import gitc_utils
Miguel Gaio1f207762020-07-17 14:09:13 +020025from git_config import GitConfig, IsId
David Pursehousee00aa6b2012-09-11 14:33:51 +090026from git_refs import R_HEADS, HEAD
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070027import platform_utils
Jack Neus6ea0cae2021-07-20 20:52:33 +000028from project import Annotation, RemoteSpec, Project, MetaProject
Mike Frysinger04122b72019-07-31 23:32:58 -040029from error import (ManifestParseError, ManifestInvalidPathError,
30 ManifestInvalidRevisionError)
Raman Tenneti993af5e2021-05-12 12:00:31 -070031from wrapper import Wrapper
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070032
33MANIFEST_FILE_NAME = 'manifest.xml'
Shawn O. Pearce5cc66792008-10-23 16:19:27 -070034LOCAL_MANIFEST_NAME = 'local_manifest.xml'
David Pursehouse2d5a0df2012-11-13 02:50:36 +090035LOCAL_MANIFESTS_DIR_NAME = 'local_manifests'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070036
Raman Tenneti78f4dd32021-06-07 13:27:37 -070037# Add all projects from local manifest into a group.
38LOCAL_MANIFEST_GROUP_PREFIX = 'local:'
39
Raman Tenneti993af5e2021-05-12 12:00:31 -070040# ContactInfo has the self-registered bug url, supplied by the manifest authors.
41ContactInfo = collections.namedtuple('ContactInfo', 'bugurl')
42
Anthony Kingcb07ba72015-03-28 23:26:04 +000043# urljoin gets confused if the scheme is not known.
Joe Kilner6e310792016-10-27 15:53:53 -070044urllib.parse.uses_relative.extend([
45 'ssh',
46 'git',
47 'persistent-https',
48 'sso',
49 'rpc'])
50urllib.parse.uses_netloc.extend([
51 'ssh',
52 'git',
53 'persistent-https',
54 'sso',
55 'rpc'])
Conley Owensdb728cd2011-09-26 16:34:01 -070056
David Pursehouse819827a2020-02-12 15:20:19 +090057
Mike Frysingerbb8ee7f2020-02-22 05:30:12 -050058def XmlBool(node, attr, default=None):
59 """Determine boolean value of |node|'s |attr|.
60
61 Invalid values will issue a non-fatal warning.
62
63 Args:
64 node: XML node whose attributes we access.
65 attr: The attribute to access.
66 default: If the attribute is not set (value is empty), then use this.
67
68 Returns:
69 True if the attribute is a valid string representing true.
70 False if the attribute is a valid string representing false.
71 |default| otherwise.
72 """
73 value = node.getAttribute(attr)
74 s = value.lower()
75 if s == '':
76 return default
77 elif s in {'yes', 'true', '1'}:
78 return True
79 elif s in {'no', 'false', '0'}:
80 return False
81 else:
82 print('warning: manifest: %s="%s": ignoring invalid XML boolean' %
83 (attr, value), file=sys.stderr)
84 return default
85
86
87def XmlInt(node, attr, default=None):
88 """Determine integer value of |node|'s |attr|.
89
90 Args:
91 node: XML node whose attributes we access.
92 attr: The attribute to access.
93 default: If the attribute is not set (value is empty), then use this.
94
95 Returns:
96 The number if the attribute is a valid number.
97
98 Raises:
99 ManifestParseError: The number is invalid.
100 """
101 value = node.getAttribute(attr)
102 if not value:
103 return default
104
105 try:
106 return int(value)
107 except ValueError:
108 raise ManifestParseError('manifest: invalid %s="%s" integer' %
109 (attr, value))
110
111
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700112class _Default(object):
113 """Project defaults within the manifest."""
114
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700115 revisionExpr = None
Conley Owensb6a16e62013-09-25 15:06:09 -0700116 destBranchExpr = None
Nasser Grainawida403412018-05-04 12:53:29 -0600117 upstreamExpr = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700118 remote = None
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700119 sync_j = 1
Anatol Pomazau79770d22012-04-20 14:41:59 -0700120 sync_c = False
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800121 sync_s = False
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900122 sync_tags = True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700123
Julien Campergue74879922013-10-09 14:38:46 +0200124 def __eq__(self, other):
Jack Neus5ba21202021-06-09 15:21:25 +0000125 if not isinstance(other, _Default):
126 return False
Julien Campergue74879922013-10-09 14:38:46 +0200127 return self.__dict__ == other.__dict__
128
129 def __ne__(self, other):
Jack Neus5ba21202021-06-09 15:21:25 +0000130 if not isinstance(other, _Default):
131 return True
Julien Campergue74879922013-10-09 14:38:46 +0200132 return self.__dict__ != other.__dict__
133
David Pursehouse819827a2020-02-12 15:20:19 +0900134
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700135class _XmlRemote(object):
136 def __init__(self,
137 name,
Yestin Sunb292b982012-07-02 07:32:50 -0700138 alias=None,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700139 fetch=None,
Steve Raed6480452016-08-10 15:00:00 -0700140 pushUrl=None,
Conley Owensdb728cd2011-09-26 16:34:01 -0700141 manifestUrl=None,
Anthony King36ea2fb2014-05-06 11:54:01 +0100142 review=None,
Jonathan Nieder93719792015-03-17 11:29:58 -0700143 revision=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700144 self.name = name
145 self.fetchUrl = fetch
Steve Raed6480452016-08-10 15:00:00 -0700146 self.pushUrl = pushUrl
Conley Owensdb728cd2011-09-26 16:34:01 -0700147 self.manifestUrl = manifestUrl
Yestin Sunb292b982012-07-02 07:32:50 -0700148 self.remoteAlias = alias
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700149 self.reviewUrl = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100150 self.revision = revision
Conley Owensceea3682011-10-20 10:45:47 -0700151 self.resolvedFetchUrl = self._resolveFetchUrl()
Jack Neus6ea0cae2021-07-20 20:52:33 +0000152 self.annotations = []
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700153
David Pursehouse717ece92012-11-13 08:49:16 +0900154 def __eq__(self, other):
Jack Neus5ba21202021-06-09 15:21:25 +0000155 if not isinstance(other, _XmlRemote):
156 return False
Jack Neus6ea0cae2021-07-20 20:52:33 +0000157 return (sorted(self.annotations) == sorted(other.annotations) and
158 self.name == other.name and self.fetchUrl == other.fetchUrl and
159 self.pushUrl == other.pushUrl and self.remoteAlias == other.remoteAlias
160 and self.reviewUrl == other.reviewUrl and self.revision == other.revision)
David Pursehouse717ece92012-11-13 08:49:16 +0900161
162 def __ne__(self, other):
Jack Neus6ea0cae2021-07-20 20:52:33 +0000163 return not self.__eq__(other)
David Pursehouse717ece92012-11-13 08:49:16 +0900164
Conley Owensceea3682011-10-20 10:45:47 -0700165 def _resolveFetchUrl(self):
Jack Neus5ba21202021-06-09 15:21:25 +0000166 if self.fetchUrl is None:
167 return ''
Conley Owensceea3682011-10-20 10:45:47 -0700168 url = self.fetchUrl.rstrip('/')
Conley Owensdb728cd2011-09-26 16:34:01 -0700169 manifestUrl = self.manifestUrl.rstrip('/')
Conley Owens2d0f5082014-01-31 15:03:51 -0800170 # urljoin will gets confused over quite a few things. The ones we care
171 # about here are:
172 # * no scheme in the base url, like
Anthony Kingcb07ba72015-03-28 23:26:04 +0000173 # We handle no scheme by replacing it with an obscure protocol, gopher
174 # and then replacing it with the original when we are done.
175
Conley Owensdb728cd2011-09-26 16:34:01 -0700176 if manifestUrl.find(':') != manifestUrl.find('/') - 1:
Conley Owens4ccad752015-04-29 10:45:37 -0700177 url = urllib.parse.urljoin('gopher://' + manifestUrl, url)
178 url = re.sub(r'^gopher://', '', url)
Anthony Kingcb07ba72015-03-28 23:26:04 +0000179 else:
180 url = urllib.parse.urljoin(manifestUrl, url)
Shawn Pearcea9f11b32013-01-02 15:40:48 -0800181 return url
Conley Owensceea3682011-10-20 10:45:47 -0700182
183 def ToRemoteSpec(self, projectName):
David Rileye0684ad2017-04-05 00:02:59 -0700184 fetchUrl = self.resolvedFetchUrl.rstrip('/')
185 url = fetchUrl + '/' + projectName
Yestin Sunb292b982012-07-02 07:32:50 -0700186 remoteName = self.name
Conley Owens1e7ab2a2013-10-08 17:26:57 -0700187 if self.remoteAlias:
David Pursehouse37128b62013-10-15 10:48:40 +0900188 remoteName = self.remoteAlias
Dan Willemsen96c2d652016-04-06 16:03:54 -0700189 return RemoteSpec(remoteName,
190 url=url,
Steve Raed6480452016-08-10 15:00:00 -0700191 pushUrl=self.pushUrl,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700192 review=self.reviewUrl,
David Rileye0684ad2017-04-05 00:02:59 -0700193 orig_name=self.name,
194 fetchUrl=self.fetchUrl)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700195
Jack Neus6ea0cae2021-07-20 20:52:33 +0000196 def AddAnnotation(self, name, value, keep):
197 self.annotations.append(Annotation(name, value, keep))
198
David Pursehouse819827a2020-02-12 15:20:19 +0900199
Shawn O. Pearcec8a300f2009-05-18 13:19:57 -0700200class XmlManifest(object):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700201 """manages the repo configuration file"""
202
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400203 def __init__(self, repodir, manifest_file, local_manifests=None):
204 """Initialize.
205
206 Args:
207 repodir: Path to the .repo/ dir for holding all internal checkout state.
208 It must be in the top directory of the repo client checkout.
209 manifest_file: Full path to the manifest file to parse. This will usually
210 be |repodir|/|MANIFEST_FILE_NAME|.
211 local_manifests: Full path to the directory of local override manifests.
212 This will usually be |repodir|/|LOCAL_MANIFESTS_DIR_NAME|.
213 """
214 # TODO(vapier): Move this out of this class.
215 self.globalConfig = GitConfig.ForUser()
216
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700217 self.repodir = os.path.abspath(repodir)
218 self.topdir = os.path.dirname(self.repodir)
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400219 self.manifestFile = manifest_file
220 self.local_manifests = local_manifests
Basil Gelloc7453502018-05-25 20:23:52 +0300221 self._load_local_manifests = True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700222
223 self.repoProject = MetaProject(self, 'repo',
David Pursehouseabdf7502020-02-12 14:58:39 +0900224 gitdir=os.path.join(repodir, 'repo/.git'),
225 worktree=os.path.join(repodir, 'repo'))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700226
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500227 mp = MetaProject(self, 'manifests',
228 gitdir=os.path.join(repodir, 'manifests.git'),
229 worktree=os.path.join(repodir, 'manifests'))
230 self.manifestProject = mp
231
232 # This is a bit hacky, but we're in a chicken & egg situation: all the
233 # normal repo settings live in the manifestProject which we just setup
234 # above, so we couldn't easily query before that. We assume Project()
235 # init doesn't care if this changes afterwards.
Mike Frysingerd957ec62020-02-24 14:40:25 -0500236 if os.path.exists(mp.gitdir) and mp.config.GetBoolean('repo.worktree'):
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500237 mp.use_git_worktrees = True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700238
239 self._Unload()
240
Basil Gelloc7453502018-05-25 20:23:52 +0300241 def Override(self, name, load_local_manifests=True):
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700242 """Use a different manifest, just for the current instantiation.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700243 """
Basil Gelloc7453502018-05-25 20:23:52 +0300244 path = None
245
246 # Look for a manifest by path in the filesystem (including the cwd).
247 if not load_local_manifests:
248 local_path = os.path.abspath(name)
249 if os.path.isfile(local_path):
250 path = local_path
251
252 # Look for manifests by name from the manifests repo.
253 if path is None:
254 path = os.path.join(self.manifestProject.worktree, name)
255 if not os.path.isfile(path):
256 raise ManifestParseError('manifest %s not found' % name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700257
258 old = self.manifestFile
259 try:
Basil Gelloc7453502018-05-25 20:23:52 +0300260 self._load_local_manifests = load_local_manifests
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700261 self.manifestFile = path
262 self._Unload()
263 self._Load()
264 finally:
265 self.manifestFile = old
266
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700267 def Link(self, name):
268 """Update the repo metadata to use a different manifest.
269 """
270 self.Override(name)
271
Mike Frysingera269b1c2020-02-21 00:49:41 -0500272 # Old versions of repo would generate symlinks we need to clean up.
273 if os.path.lexists(self.manifestFile):
274 platform_utils.remove(self.manifestFile)
275 # This file is interpreted as if it existed inside the manifest repo.
276 # That allows us to use with the relative file name.
277 with open(self.manifestFile, 'w') as fp:
278 fp.write("""
279
289
290
291
292""" % (name,))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700293
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800294 def _RemoteToXml(self, r, doc, root):
295 e = doc.createElement('remote')
296 root.appendChild(e)
297 e.setAttribute('name', r.name)
298 e.setAttribute('fetch', r.fetchUrl)
Steve Raed6480452016-08-10 15:00:00 -0700299 if r.pushUrl is not None:
300 e.setAttribute('pushurl', r.pushUrl)
Conley Owens1e7ab2a2013-10-08 17:26:57 -0700301 if r.remoteAlias is not None:
302 e.setAttribute('alias', r.remoteAlias)
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800303 if r.reviewUrl is not None:
304 e.setAttribute('review', r.reviewUrl)
Anthony King36ea2fb2014-05-06 11:54:01 +0100305 if r.revision is not None:
306 e.setAttribute('revision', r.revision)
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800307
Jack Neus6ea0cae2021-07-20 20:52:33 +0000308 for a in r.annotations:
309 if a.keep == 'true':
310 ae = doc.createElement('annotation')
311 ae.setAttribute('name', a.name)
312 ae.setAttribute('value', a.value)
313 e.appendChild(ae)
314
Mike Frysinger51e39d52020-12-04 05:32:06 -0500315 def _ParseList(self, field):
316 """Parse fields that contain flattened lists.
317
318 These are whitespace & comma separated. Empty elements will be discarded.
319 """
320 return [x for x in re.split(r'[,\s]+', field) if x]
Josh Triplett884a3872014-06-12 14:57:29 -0700321
Mike Frysinger23411d32020-09-02 04:31:10 -0400322 def ToXml(self, peg_rev=False, peg_rev_upstream=True, peg_rev_dest_branch=True, groups=None):
323 """Return the current manifest XML."""
Colin Cross5acde752012-03-28 20:15:45 -0700324 mp = self.manifestProject
325
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700326 if groups is None:
327 groups = mp.config.GetString('manifest.groups')
Matt Gumbel0c635bb2012-12-21 10:14:53 -0800328 if groups:
Mike Frysinger51e39d52020-12-04 05:32:06 -0500329 groups = self._ParseList(groups)
Colin Cross5acde752012-03-28 20:15:45 -0700330
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800331 doc = xml.dom.minidom.Document()
332 root = doc.createElement('manifest')
333 doc.appendChild(root)
334
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700335 # Save out the notice. There's a little bit of work here to give it the
336 # right whitespace, which assumes that the notice is automatically indented
337 # by 4 by minidom.
338 if self.notice:
339 notice_element = root.appendChild(doc.createElement('notice'))
340 notice_lines = self.notice.splitlines()
David Pursehouse54a4e602020-02-12 14:31:05 +0900341 indented_notice = ('\n'.join(" " * 4 + line for line in notice_lines))[4:]
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700342 notice_element.appendChild(doc.createTextNode(indented_notice))
343
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800344 d = self.default
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800345
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530346 for r in sorted(self.remotes):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800347 self._RemoteToXml(self.remotes[r], doc, root)
348 if self.remotes:
349 root.appendChild(doc.createTextNode(''))
350
351 have_default = False
352 e = doc.createElement('default')
353 if d.remote:
354 have_default = True
355 e.setAttribute('remote', d.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700356 if d.revisionExpr:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800357 have_default = True
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700358 e.setAttribute('revision', d.revisionExpr)
Simon Ruggier7e59de22015-07-24 12:50:06 +0200359 if d.destBranchExpr:
360 have_default = True
361 e.setAttribute('dest-branch', d.destBranchExpr)
Nasser Grainawida403412018-05-04 12:53:29 -0600362 if d.upstreamExpr:
363 have_default = True
364 e.setAttribute('upstream', d.upstreamExpr)
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700365 if d.sync_j > 1:
366 have_default = True
367 e.setAttribute('sync-j', '%d' % d.sync_j)
Anatol Pomazau79770d22012-04-20 14:41:59 -0700368 if d.sync_c:
369 have_default = True
370 e.setAttribute('sync-c', 'true')
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800371 if d.sync_s:
372 have_default = True
373 e.setAttribute('sync-s', 'true')
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900374 if not d.sync_tags:
375 have_default = True
376 e.setAttribute('sync-tags', 'false')
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800377 if have_default:
378 root.appendChild(e)
379 root.appendChild(doc.createTextNode(''))
380
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700381 if self._manifest_server:
382 e = doc.createElement('manifest-server')
383 e.setAttribute('url', self._manifest_server)
384 root.appendChild(e)
385 root.appendChild(doc.createTextNode(''))
386
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800387 def output_projects(parent, parent_node, projects):
David James8d201162013-10-11 17:03:19 -0700388 for project_name in projects:
389 for project in self._projects[project_name]:
390 output_project(parent, parent_node, project)
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800391
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800392 def output_project(parent, parent_node, p):
Colin Cross5acde752012-03-28 20:15:45 -0700393 if not p.MatchesGroups(groups):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800394 return
395
396 name = p.name
397 relpath = p.relpath
398 if parent:
399 name = self._UnjoinName(parent.name, name)
400 relpath = self._UnjoinRelpath(parent.relpath, relpath)
Colin Cross5acde752012-03-28 20:15:45 -0700401
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800402 e = doc.createElement('project')
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800403 parent_node.appendChild(e)
404 e.setAttribute('name', name)
405 if relpath != name:
406 e.setAttribute('path', relpath)
Conley Owensa17d7af2013-10-16 14:38:09 -0700407 remoteName = None
408 if d.remote:
Dan Willemsen96c2d652016-04-06 16:03:54 -0700409 remoteName = d.remote.name
410 if not d.remote or p.remote.orig_name != remoteName:
411 remoteName = p.remote.orig_name
Anthony King36ea2fb2014-05-06 11:54:01 +0100412 e.setAttribute('remote', remoteName)
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800413 if peg_rev:
414 if self.IsMirror:
Brian Harring14a66742012-09-28 20:21:57 -0700415 value = p.bare_git.rev_parse(p.revisionExpr + '^0')
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800416 else:
Brian Harring14a66742012-09-28 20:21:57 -0700417 value = p.work_git.rev_parse(HEAD + '^0')
418 e.setAttribute('revision', value)
Conley Owens551dfec2015-07-10 14:54:54 -0700419 if peg_rev_upstream:
420 if p.upstream:
421 e.setAttribute('upstream', p.upstream)
422 elif value != p.revisionExpr:
423 # Only save the origin if the origin is not a sha1, and the default
424 # isn't our value
425 e.setAttribute('upstream', p.revisionExpr)
Sean McAllisteraf908cb2020-04-20 08:41:58 -0600426
427 if peg_rev_dest_branch:
428 if p.dest_branch:
429 e.setAttribute('dest-branch', p.dest_branch)
430 elif value != p.revisionExpr:
431 e.setAttribute('dest-branch', p.revisionExpr)
432
Anthony King36ea2fb2014-05-06 11:54:01 +0100433 else:
Dan Willemsen96c2d652016-04-06 16:03:54 -0700434 revision = self.remotes[p.remote.orig_name].revision or d.revisionExpr
Anthony King36ea2fb2014-05-06 11:54:01 +0100435 if not revision or revision != p.revisionExpr:
436 e.setAttribute('revision', p.revisionExpr)
Raman Tennetib5c5a5e2021-02-06 09:44:15 -0800437 elif p.revisionId:
438 e.setAttribute('revision', p.revisionId)
Nasser Grainawida403412018-05-04 12:53:29 -0600439 if (p.upstream and (p.upstream != p.revisionExpr or
440 p.upstream != d.upstreamExpr)):
Mani Chandel7a91d512014-07-24 16:27:08 +0530441 e.setAttribute('upstream', p.upstream)
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800442
Simon Ruggier7e59de22015-07-24 12:50:06 +0200443 if p.dest_branch and p.dest_branch != d.destBranchExpr:
444 e.setAttribute('dest-branch', p.dest_branch)
445
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800446 for c in p.copyfiles:
447 ce = doc.createElement('copyfile')
448 ce.setAttribute('src', c.src)
449 ce.setAttribute('dest', c.dest)
450 e.appendChild(ce)
451
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500452 for l in p.linkfiles:
453 le = doc.createElement('linkfile')
454 le.setAttribute('src', l.src)
455 le.setAttribute('dest', l.dest)
456 e.appendChild(le)
457
Conley Owensbb1b5f52012-08-13 13:11:18 -0700458 default_groups = ['all', 'name:%s' % p.name, 'path:%s' % p.relpath]
Dmitry Fink17f85ea2012-08-06 14:52:29 -0700459 egroups = [g for g in p.groups if g not in default_groups]
Conley Owens971de8e2012-04-16 10:36:08 -0700460 if egroups:
461 e.setAttribute('groups', ','.join(egroups))
Colin Cross5acde752012-03-28 20:15:45 -0700462
James W. Mills24c13082012-04-12 15:04:13 -0500463 for a in p.annotations:
464 if a.keep == "true":
465 ae = doc.createElement('annotation')
466 ae.setAttribute('name', a.name)
467 ae.setAttribute('value', a.value)
468 e.appendChild(ae)
469
Anatol Pomazau79770d22012-04-20 14:41:59 -0700470 if p.sync_c:
471 e.setAttribute('sync-c', 'true')
472
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800473 if p.sync_s:
474 e.setAttribute('sync-s', 'true')
475
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900476 if not p.sync_tags:
477 e.setAttribute('sync-tags', 'false')
478
Dan Willemsen88409222015-08-17 15:29:10 -0700479 if p.clone_depth:
480 e.setAttribute('clone-depth', str(p.clone_depth))
481
Simran Basib9a1b732015-08-20 12:19:28 -0700482 self._output_manifest_project_extras(p, e)
483
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800484 if p.subprojects:
David James8d201162013-10-11 17:03:19 -0700485 subprojects = set(subp.name for subp in p.subprojects)
486 output_projects(p, e, list(sorted(subprojects)))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800487
David James8d201162013-10-11 17:03:19 -0700488 projects = set(p.name for p in self._paths.values() if not p.parent)
489 output_projects(None, root, list(sorted(projects)))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800490
Doug Anderson37282b42011-03-04 11:54:18 -0800491 if self._repo_hooks_project:
492 root.appendChild(doc.createTextNode(''))
493 e = doc.createElement('repo-hooks')
494 e.setAttribute('in-project', self._repo_hooks_project.name)
495 e.setAttribute('enabled-list',
496 ' '.join(self._repo_hooks_project.enabled_repo_hooks))
497 root.appendChild(e)
498
Raman Tenneti1bb4fb22021-01-07 16:50:45 -0800499 if self._superproject:
500 root.appendChild(doc.createTextNode(''))
501 e = doc.createElement('superproject')
502 e.setAttribute('name', self._superproject['name'])
503 remoteName = None
504 if d.remote:
505 remoteName = d.remote.name
506 remote = self._superproject.get('remote')
507 if not d.remote or remote.orig_name != remoteName:
508 remoteName = remote.orig_name
509 e.setAttribute('remote', remoteName)
Xin Lie0b16a22021-09-26 23:20:32 -0700510 revision = remote.revision or d.revisionExpr
511 if not revision or revision != self._superproject['revision']:
512 e.setAttribute('revision', self._superproject['revision'])
Raman Tenneti1bb4fb22021-01-07 16:50:45 -0800513 root.appendChild(e)
514
Raman Tenneti993af5e2021-05-12 12:00:31 -0700515 if self._contactinfo.bugurl != Wrapper().BUG_URL:
Raman Tenneti1c3f57e2021-05-04 12:32:13 -0700516 root.appendChild(doc.createTextNode(''))
517 e = doc.createElement('contactinfo')
Raman Tenneti993af5e2021-05-12 12:00:31 -0700518 e.setAttribute('bugurl', self._contactinfo.bugurl)
Raman Tenneti1c3f57e2021-05-04 12:32:13 -0700519 root.appendChild(e)
520
Mike Frysinger23411d32020-09-02 04:31:10 -0400521 return doc
522
523 def ToDict(self, **kwargs):
524 """Return the current manifest as a dictionary."""
525 # Elements that may only appear once.
526 SINGLE_ELEMENTS = {
527 'notice',
528 'default',
529 'manifest-server',
530 'repo-hooks',
Raman Tenneti1bb4fb22021-01-07 16:50:45 -0800531 'superproject',
Raman Tenneti1c3f57e2021-05-04 12:32:13 -0700532 'contactinfo',
Mike Frysinger23411d32020-09-02 04:31:10 -0400533 }
534 # Elements that may be repeated.
535 MULTI_ELEMENTS = {
536 'remote',
537 'remove-project',
538 'project',
539 'extend-project',
540 'include',
541 # These are children of 'project' nodes.
542 'annotation',
543 'project',
544 'copyfile',
545 'linkfile',
546 }
547
548 doc = self.ToXml(**kwargs)
549 ret = {}
550
551 def append_children(ret, node):
552 for child in node.childNodes:
553 if child.nodeType == xml.dom.Node.ELEMENT_NODE:
554 attrs = child.attributes
555 element = dict((attrs.item(i).localName, attrs.item(i).value)
556 for i in range(attrs.length))
557 if child.nodeName in SINGLE_ELEMENTS:
558 ret[child.nodeName] = element
559 elif child.nodeName in MULTI_ELEMENTS:
560 ret.setdefault(child.nodeName, []).append(element)
561 else:
562 raise ManifestParseError('Unhandled element "%s"' % (child.nodeName,))
563
564 append_children(element, child)
565
566 append_children(ret, doc.firstChild)
567
568 return ret
569
570 def Save(self, fd, **kwargs):
571 """Write the current manifest out to the given file descriptor."""
572 doc = self.ToXml(**kwargs)
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800573 doc.writexml(fd, '', ' ', '\n', 'UTF-8')
574
Simran Basib9a1b732015-08-20 12:19:28 -0700575 def _output_manifest_project_extras(self, p, e):
576 """Manifests can modify e if they support extra project attributes."""
Simran Basib9a1b732015-08-20 12:19:28 -0700577
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700578 @property
David James8d201162013-10-11 17:03:19 -0700579 def paths(self):
580 self._Load()
581 return self._paths
582
583 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700584 def projects(self):
585 self._Load()
Anthony Kingd58bfe52014-05-05 23:30:49 +0100586 return list(self._paths.values())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700587
588 @property
589 def remotes(self):
590 self._Load()
591 return self._remotes
592
593 @property
594 def default(self):
595 self._Load()
596 return self._default
597
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800598 @property
Doug Anderson37282b42011-03-04 11:54:18 -0800599 def repo_hooks_project(self):
600 self._Load()
601 return self._repo_hooks_project
602
603 @property
Raman Tenneti1bb4fb22021-01-07 16:50:45 -0800604 def superproject(self):
605 self._Load()
606 return self._superproject
607
608 @property
Raman Tenneti1c3f57e2021-05-04 12:32:13 -0700609 def contactinfo(self):
610 self._Load()
611 return self._contactinfo
612
613 @property
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700614 def notice(self):
615 self._Load()
616 return self._notice
617
618 @property
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700619 def manifest_server(self):
620 self._Load()
Shawn O. Pearce34fb20f2011-11-30 13:41:02 -0800621 return self._manifest_server
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700622
623 @property
Xin Lid79a4bc2020-05-20 16:03:45 -0700624 def CloneBundle(self):
625 clone_bundle = self.manifestProject.config.GetBoolean('repo.clonebundle')
626 if clone_bundle is None:
627 return False if self.manifestProject.config.GetBoolean('repo.partialclone') else True
628 else:
629 return clone_bundle
630
631 @property
Xin Li745be2e2019-06-03 11:24:30 -0700632 def CloneFilter(self):
633 if self.manifestProject.config.GetBoolean('repo.partialclone'):
634 return self.manifestProject.config.GetString('repo.clonefilter')
635 return None
636
637 @property
Raman Tennetif32f2432021-04-12 20:57:25 -0700638 def PartialCloneExclude(self):
639 exclude = self.manifest.manifestProject.config.GetString(
640 'repo.partialcloneexclude') or ''
641 return set(x.strip() for x in exclude.split(','))
642
643 @property
Michael Kellyc34b91c2021-07-02 09:25:48 -0700644 def UseLocalManifests(self):
645 return self._load_local_manifests
646
647 def SetUseLocalManifests(self, value):
648 self._load_local_manifests = value
649
650 @property
Raman Tennetifeb28912021-05-02 19:47:29 -0700651 def HasLocalManifests(self):
652 return self._load_local_manifests and self.local_manifests
653
654 @property
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800655 def IsMirror(self):
656 return self.manifestProject.config.GetBoolean('repo.mirror')
657
Julien Campergue335f5ef2013-10-16 11:02:35 +0200658 @property
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500659 def UseGitWorktrees(self):
660 return self.manifestProject.config.GetBoolean('repo.worktree')
661
662 @property
Julien Campergue335f5ef2013-10-16 11:02:35 +0200663 def IsArchive(self):
664 return self.manifestProject.config.GetBoolean('repo.archive')
665
Martin Kellye4e94d22017-03-21 16:05:12 -0700666 @property
667 def HasSubmodules(self):
668 return self.manifestProject.config.GetBoolean('repo.submodules')
669
Raman Tenneti080877e2021-03-09 15:19:06 -0800670 def GetDefaultGroupsStr(self):
671 """Returns the default group string for the platform."""
672 return 'default,platform-' + platform.system().lower()
673
674 def GetGroupsStr(self):
675 """Returns the manifest group string that should be synced."""
676 groups = self.manifestProject.config.GetString('manifest.groups')
677 if not groups:
678 groups = self.GetDefaultGroupsStr()
679 return groups
680
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700681 def _Unload(self):
682 self._loaded = False
683 self._projects = {}
David James8d201162013-10-11 17:03:19 -0700684 self._paths = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700685 self._remotes = {}
686 self._default = None
Doug Anderson37282b42011-03-04 11:54:18 -0800687 self._repo_hooks_project = None
Raman Tenneti1bb4fb22021-01-07 16:50:45 -0800688 self._superproject = {}
Raman Tenneti993af5e2021-05-12 12:00:31 -0700689 self._contactinfo = ContactInfo(Wrapper().BUG_URL)
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700690 self._notice = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700691 self.branch = None
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700692 self._manifest_server = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700693
694 def _Load(self):
695 if not self._loaded:
Shawn O. Pearce2450a292008-11-04 08:22:07 -0800696 m = self.manifestProject
697 b = m.GetBranch(m.CurrentBranch).merge
Shawn O. Pearce21c5c342009-06-25 16:47:30 -0700698 if b is not None and b.startswith(R_HEADS):
Shawn O. Pearce2450a292008-11-04 08:22:07 -0800699 b = b[len(R_HEADS):]
700 self.branch = b
701
Mike Frysinger54133972021-03-01 21:38:08 -0500702 # The manifestFile was specified by the user which is why we allow include
703 # paths to point anywhere.
Colin Cross23acdd32012-04-21 00:33:54 -0700704 nodes = []
Mike Frysinger54133972021-03-01 21:38:08 -0500705 nodes.append(self._ParseManifestXml(
706 self.manifestFile, self.manifestProject.worktree,
707 restrict_includes=False))
Shawn O. Pearce5cc66792008-10-23 16:19:27 -0700708
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400709 if self._load_local_manifests and self.local_manifests:
Basil Gelloc7453502018-05-25 20:23:52 +0300710 try:
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400711 for local_file in sorted(platform_utils.listdir(self.local_manifests)):
Basil Gelloc7453502018-05-25 20:23:52 +0300712 if local_file.endswith('.xml'):
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400713 local = os.path.join(self.local_manifests, local_file)
Mike Frysinger54133972021-03-01 21:38:08 -0500714 # Since local manifests are entirely managed by the user, allow
715 # them to point anywhere the user wants.
716 nodes.append(self._ParseManifestXml(
Raman Tenneti78f4dd32021-06-07 13:27:37 -0700717 local, self.repodir,
718 parent_groups=f'{LOCAL_MANIFEST_GROUP_PREFIX}:{local_file[:-4]}',
719 restrict_includes=False))
Basil Gelloc7453502018-05-25 20:23:52 +0300720 except OSError:
721 pass
David Pursehouse2d5a0df2012-11-13 02:50:36 +0900722
Joe Onorato26e24752013-01-11 12:35:53 -0800723 try:
724 self._ParseManifest(nodes)
725 except ManifestParseError as e:
726 # There was a problem parsing, unload ourselves in case they catch
727 # this error and try again later, we will show the correct error
728 self._Unload()
729 raise e
Shawn O. Pearce5cc66792008-10-23 16:19:27 -0700730
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800731 if self.IsMirror:
732 self._AddMetaProjectMirror(self.repoProject)
733 self._AddMetaProjectMirror(self.manifestProject)
734
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700735 self._loaded = True
736
Mike Frysinger54133972021-03-01 21:38:08 -0500737 def _ParseManifestXml(self, path, include_root, parent_groups='',
738 restrict_includes=True):
739 """Parse a manifest XML and return the computed nodes.
740
741 Args:
742 path: The XML file to read & parse.
743 include_root: The path to interpret include "name"s relative to.
744 parent_groups: The groups to apply to this projects.
745 restrict_includes: Whether to constrain the "name" attribute of includes.
746
747 Returns:
748 List of XML nodes.
749 """
David Pursehousef7fc8a92012-11-13 04:00:28 +0900750 try:
751 root = xml.dom.minidom.parse(path)
David Pursehouse2d5a0df2012-11-13 02:50:36 +0900752 except (OSError, xml.parsers.expat.ExpatError) as e:
David Pursehousef7fc8a92012-11-13 04:00:28 +0900753 raise ManifestParseError("error parsing manifest %s: %s" % (path, e))
754
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700755 if not root or not root.childNodes:
Brian Harring26448742011-04-28 05:04:41 -0700756 raise ManifestParseError("no root node in %s" % (path,))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700757
Jooncheol Park34acdd22012-08-27 02:25:59 +0900758 for manifest in root.childNodes:
759 if manifest.nodeName == 'manifest':
760 break
761 else:
Brian Harring26448742011-04-28 05:04:41 -0700762 raise ManifestParseError("no in %s" % (path,))
763
Colin Cross23acdd32012-04-21 00:33:54 -0700764 nodes = []
David Pursehouse65b0ba52018-06-24 16:21:51 +0900765 for node in manifest.childNodes:
David Pursehousec1b86a22012-11-14 11:36:51 +0900766 if node.nodeName == 'include':
767 name = self._reqatt(node, 'name')
Mike Frysinger54133972021-03-01 21:38:08 -0500768 if restrict_includes:
769 msg = self._CheckLocalPath(name)
770 if msg:
771 raise ManifestInvalidPathError(
772 ' invalid "name": %s: %s' % (name, msg))
Fredrik de Groot352c93b2020-10-06 12:55:14 +0200773 include_groups = ''
774 if parent_groups:
775 include_groups = parent_groups
776 if node.hasAttribute('groups'):
777 include_groups = node.getAttribute('groups') + ',' + include_groups
David Pursehousec1b86a22012-11-14 11:36:51 +0900778 fp = os.path.join(include_root, name)
779 if not os.path.isfile(fp):
Mike Frysinger54133972021-03-01 21:38:08 -0500780 raise ManifestParseError("include [%s/]%s doesn't exist or isn't a file"
781 % (include_root, name))
David Pursehousec1b86a22012-11-14 11:36:51 +0900782 try:
Fredrik de Groot352c93b2020-10-06 12:55:14 +0200783 nodes.extend(self._ParseManifestXml(fp, include_root, include_groups))
David Pursehousec1b86a22012-11-14 11:36:51 +0900784 # should isolate this to the exact exception, but that's
785 # tricky. actual parsing implementation may vary.
Mike Frysinger54133972021-03-01 21:38:08 -0500786 except (KeyboardInterrupt, RuntimeError, SystemExit, ManifestParseError):
David Pursehousec1b86a22012-11-14 11:36:51 +0900787 raise
788 except Exception as e:
789 raise ManifestParseError(
Mike Frysingerec558df2019-07-05 01:38:05 -0400790 "failed parsing included manifest %s: %s" % (name, e))
David Pursehousec1b86a22012-11-14 11:36:51 +0900791 else:
Fredrik de Groot352c93b2020-10-06 12:55:14 +0200792 if parent_groups and node.nodeName == 'project':
793 nodeGroups = parent_groups
794 if node.hasAttribute('groups'):
795 nodeGroups = node.getAttribute('groups') + ',' + nodeGroups
796 node.setAttribute('groups', nodeGroups)
David Pursehousec1b86a22012-11-14 11:36:51 +0900797 nodes.append(node)
Colin Cross23acdd32012-04-21 00:33:54 -0700798 return nodes
Brian Harring26448742011-04-28 05:04:41 -0700799
Colin Cross23acdd32012-04-21 00:33:54 -0700800 def _ParseManifest(self, node_list):
801 for node in itertools.chain(*node_list):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700802 if node.nodeName == 'remote':
803 remote = self._ParseRemote(node)
David Pursehouse717ece92012-11-13 08:49:16 +0900804 if remote:
805 if remote.name in self._remotes:
806 if remote != self._remotes[remote.name]:
807 raise ManifestParseError(
808 'remote %s already exists with different attributes' %
809 (remote.name))
810 else:
811 self._remotes[remote.name] = remote
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700812
Colin Cross23acdd32012-04-21 00:33:54 -0700813 for node in itertools.chain(*node_list):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700814 if node.nodeName == 'default':
Julien Campergue74879922013-10-09 14:38:46 +0200815 new_default = self._ParseDefault(node)
Jack Neusb8c84482021-06-15 14:28:30 +0000816 emptyDefault = not node.hasAttributes() and not node.hasChildNodes()
Julien Campergue74879922013-10-09 14:38:46 +0200817 if self._default is None:
818 self._default = new_default
Jack Neusb8c84482021-06-15 14:28:30 +0000819 elif not emptyDefault and new_default != self._default:
David Pursehouse37128b62013-10-15 10:48:40 +0900820 raise ManifestParseError('duplicate default in %s' %
821 (self.manifestFile))
Julien Campergue74879922013-10-09 14:38:46 +0200822
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700823 if self._default is None:
824 self._default = _Default()
825
Colin Cross23acdd32012-04-21 00:33:54 -0700826 for node in itertools.chain(*node_list):
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700827 if node.nodeName == 'notice':
828 if self._notice is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800829 raise ManifestParseError(
830 'duplicate notice in %s' %
831 (self.manifestFile))
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700832 self._notice = self._ParseNotice(node)
833
Colin Cross23acdd32012-04-21 00:33:54 -0700834 for node in itertools.chain(*node_list):
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700835 if node.nodeName == 'manifest-server':
836 url = self._reqatt(node, 'url')
837 if self._manifest_server is not None:
David Pursehousec1b86a22012-11-14 11:36:51 +0900838 raise ManifestParseError(
839 'duplicate manifest-server in %s' %
840 (self.manifestFile))
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700841 self._manifest_server = url
842
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800843 def recursively_add_projects(project):
David James8d201162013-10-11 17:03:19 -0700844 projects = self._projects.setdefault(project.name, [])
845 if project.relpath is None:
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800846 raise ManifestParseError(
David James8d201162013-10-11 17:03:19 -0700847 'missing path for %s in %s' %
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800848 (project.name, self.manifestFile))
David James8d201162013-10-11 17:03:19 -0700849 if project.relpath in self._paths:
850 raise ManifestParseError(
851 'duplicate path %s in %s' %
852 (project.relpath, self.manifestFile))
853 self._paths[project.relpath] = project
854 projects.append(project)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800855 for subproject in project.subprojects:
856 recursively_add_projects(subproject)
857
Jack Neusa84f43a2021-09-21 22:23:55 +0000858 repo_hooks_project = None
859 enabled_repo_hooks = None
Colin Cross23acdd32012-04-21 00:33:54 -0700860 for node in itertools.chain(*node_list):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700861 if node.nodeName == 'project':
862 project = self._ParseProject(node)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800863 recursively_add_projects(project)
Josh Triplett884a3872014-06-12 14:57:29 -0700864 if node.nodeName == 'extend-project':
865 name = self._reqatt(node, 'name')
866
867 if name not in self._projects:
868 raise ManifestParseError('extend-project element specifies non-existent '
869 'project: %s' % name)
870
871 path = node.getAttribute('path')
872 groups = node.getAttribute('groups')
873 if groups:
Mike Frysinger51e39d52020-12-04 05:32:06 -0500874 groups = self._ParseList(groups)
Luis Hector Chavez7d525852018-03-15 09:54:08 -0700875 revision = node.getAttribute('revision')
Kyunam Jobd0aae92020-02-04 11:38:53 +0900876 remote = node.getAttribute('remote')
877 if remote:
878 remote = self._get_remote(node)
Josh Triplett884a3872014-06-12 14:57:29 -0700879
880 for p in self._projects[name]:
881 if path and p.relpath != path:
882 continue
883 if groups:
884 p.groups.extend(groups)
Luis Hector Chavez7d525852018-03-15 09:54:08 -0700885 if revision:
886 p.revisionExpr = revision
Miguel Gaio1f207762020-07-17 14:09:13 +0200887 if IsId(revision):
888 p.revisionId = revision
889 else:
890 p.revisionId = None
Kyunam Jobd0aae92020-02-04 11:38:53 +0900891 if remote:
892 p.remote = remote.ToRemoteSpec(name)
Doug Anderson37282b42011-03-04 11:54:18 -0800893 if node.nodeName == 'repo-hooks':
Doug Anderson37282b42011-03-04 11:54:18 -0800894 # Only one project can be the hooks project
Jack Neusa84f43a2021-09-21 22:23:55 +0000895 if repo_hooks_project is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800896 raise ManifestParseError(
897 'duplicate repo-hooks in %s' %
898 (self.manifestFile))
899
Jack Neusa84f43a2021-09-21 22:23:55 +0000900 # Get the name of the project and the (space-separated) list of enabled.
901 repo_hooks_project = self._reqatt(node, 'in-project')
902 enabled_repo_hooks = self._ParseList(self._reqatt(node, 'enabled-list'))
Raman Tenneti1bb4fb22021-01-07 16:50:45 -0800903 if node.nodeName == 'superproject':
904 name = self._reqatt(node, 'name')
905 # There can only be one superproject.
906 if self._superproject.get('name'):
907 raise ManifestParseError(
908 'duplicate superproject in %s' %
909 (self.manifestFile))
910 self._superproject['name'] = name
911 remote_name = node.getAttribute('remote')
912 if not remote_name:
913 remote = self._default.remote
914 else:
915 remote = self._get_remote(node)
916 if remote is None:
917 raise ManifestParseError("no remote for superproject %s within %s" %
918 (name, self.manifestFile))
919 self._superproject['remote'] = remote.ToRemoteSpec(name)
Xin Lie0b16a22021-09-26 23:20:32 -0700920 revision = node.getAttribute('revision') or remote.revision
921 if not revision:
922 revision = self._default.revisionExpr
923 if not revision:
924 raise ManifestParseError('no revision for superproject %s within %s' %
925 (name, self.manifestFile))
926 self._superproject['revision'] = revision
Raman Tenneti1c3f57e2021-05-04 12:32:13 -0700927 if node.nodeName == 'contactinfo':
928 bugurl = self._reqatt(node, 'bugurl')
929 # This element can be repeated, later entries will clobber earlier ones.
Raman Tenneti993af5e2021-05-12 12:00:31 -0700930 self._contactinfo = ContactInfo(bugurl)
931
Colin Cross23acdd32012-04-21 00:33:54 -0700932 if node.nodeName == 'remove-project':
933 name = self._reqatt(node, 'name')
David Jamesb8433df2014-01-30 10:11:17 -0800934
Michael Kelly06da9982021-06-30 01:58:28 -0700935 if name in self._projects:
936 for p in self._projects[name]:
937 del self._paths[p.relpath]
938 del self._projects[name]
939
940 # If the manifest removes the hooks project, treat it as if it deleted
941 # the repo-hooks element too.
Jack Neusa84f43a2021-09-21 22:23:55 +0000942 if repo_hooks_project == name:
943 repo_hooks_project = None
Michael Kelly06da9982021-06-30 01:58:28 -0700944 elif not XmlBool(node, 'optional', False):
David Pursehousef9107482012-11-16 19:12:32 +0900945 raise ManifestParseError('remove-project element specifies non-existent '
946 'project: %s' % name)
Colin Cross23acdd32012-04-21 00:33:54 -0700947
Jack Neusa84f43a2021-09-21 22:23:55 +0000948 # Store repo hooks project information.
949 if repo_hooks_project:
950 # Store a reference to the Project.
951 try:
952 repo_hooks_projects = self._projects[repo_hooks_project]
953 except KeyError:
954 raise ManifestParseError(
955 'project %s not found for repo-hooks' %
956 (repo_hooks_project))
957
958 if len(repo_hooks_projects) != 1:
959 raise ManifestParseError(
960 'internal error parsing repo-hooks in %s' %
961 (self.manifestFile))
962 self._repo_hooks_project = repo_hooks_projects[0]
963 # Store the enabled hooks in the Project object.
964 self._repo_hooks_project.enabled_repo_hooks = enabled_repo_hooks
965
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800966 def _AddMetaProjectMirror(self, m):
967 name = None
968 m_url = m.GetRemote(m.remote.name).url
969 if m_url.endswith('/.git'):
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530970 raise ManifestParseError('refusing to mirror %s' % m_url)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800971
972 if self._default and self._default.remote:
Conley Owensceea3682011-10-20 10:45:47 -0700973 url = self._default.remote.resolvedFetchUrl
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800974 if not url.endswith('/'):
975 url += '/'
976 if m_url.startswith(url):
977 remote = self._default.remote
978 name = m_url[len(url):]
979
980 if name is None:
981 s = m_url.rindex('/') + 1
Conley Owensdb728cd2011-09-26 16:34:01 -0700982 manifestUrl = self.manifestProject.config.GetString('remote.origin.url')
Shawn O. Pearcef35b2d92012-08-02 11:46:22 -0700983 remote = _XmlRemote('origin', fetch=m_url[:s], manifestUrl=manifestUrl)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800984 name = m_url[s:]
985
986 if name.endswith('.git'):
987 name = name[:-4]
988
989 if name not in self._projects:
990 m.PreSync()
991 gitdir = os.path.join(self.topdir, '%s.git' % name)
David Pursehousee5913ae2020-02-12 13:56:59 +0900992 project = Project(manifest=self,
993 name=name,
994 remote=remote.ToRemoteSpec(name),
995 gitdir=gitdir,
996 objdir=gitdir,
997 worktree=None,
998 relpath=name or None,
999 revisionExpr=m.revisionExpr,
1000 revisionId=None)
David James8d201162013-10-11 17:03:19 -07001001 self._projects[project.name] = [project]
Kwanhong Leeccd218c2014-02-17 13:07:32 +09001002 self._paths[project.relpath] = project
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001003
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001004 def _ParseRemote(self, node):
1005 """
1006 reads a element from the manifest file
1007 """
1008 name = self._reqatt(node, 'name')
Yestin Sunb292b982012-07-02 07:32:50 -07001009 alias = node.getAttribute('alias')
1010 if alias == '':
1011 alias = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001012 fetch = self._reqatt(node, 'fetch')
Steve Raed6480452016-08-10 15:00:00 -07001013 pushUrl = node.getAttribute('pushurl')
1014 if pushUrl == '':
1015 pushUrl = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001016 review = node.getAttribute('review')
Shawn O. Pearceae6e0942008-11-06 10:25:35 -08001017 if review == '':
1018 review = None
Anthony King36ea2fb2014-05-06 11:54:01 +01001019 revision = node.getAttribute('revision')
1020 if revision == '':
1021 revision = None
Conley Owensdb728cd2011-09-26 16:34:01 -07001022 manifestUrl = self.manifestProject.config.GetString('remote.origin.url')
Jack Neus6ea0cae2021-07-20 20:52:33 +00001023
1024 remote = _XmlRemote(name, alias, fetch, pushUrl, manifestUrl, review, revision)
1025
1026 for n in node.childNodes:
1027 if n.nodeName == 'annotation':
1028 self._ParseAnnotation(remote, n)
1029
1030 return remote
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001031
1032 def _ParseDefault(self, node):
1033 """
1034 reads a element from the manifest file
1035 """
1036 d = _Default()
1037 d.remote = self._get_remote(node)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001038 d.revisionExpr = node.getAttribute('revision')
1039 if d.revisionExpr == '':
1040 d.revisionExpr = None
Anatol Pomazau79770d22012-04-20 14:41:59 -07001041
Bryan Jacobsf609f912013-05-06 13:36:24 -04001042 d.destBranchExpr = node.getAttribute('dest-branch') or None
Nasser Grainawida403412018-05-04 12:53:29 -06001043 d.upstreamExpr = node.getAttribute('upstream') or None
Bryan Jacobsf609f912013-05-06 13:36:24 -04001044
Mike Frysingerbb8ee7f2020-02-22 05:30:12 -05001045 d.sync_j = XmlInt(node, 'sync-j', 1)
1046 if d.sync_j <= 0:
1047 raise ManifestParseError('%s: sync-j must be greater than 0, not "%s"' %
1048 (self.manifestFile, d.sync_j))
Anatol Pomazau79770d22012-04-20 14:41:59 -07001049
Mike Frysingerbb8ee7f2020-02-22 05:30:12 -05001050 d.sync_c = XmlBool(node, 'sync-c', False)
1051 d.sync_s = XmlBool(node, 'sync-s', False)
1052 d.sync_tags = XmlBool(node, 'sync-tags', True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001053 return d
1054
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001055 def _ParseNotice(self, node):
1056 """
1057 reads a element from the manifest file
1058
1059 The element is distinct from other tags in the XML in that the
1060 data is conveyed between the start and end tag (it's not an empty-element
1061 tag).
1062
1063 The white space (carriage returns, indentation) for the notice element is
1064 relevant and is parsed in a way that is based on how python docstrings work.
1065 In fact, the code is remarkably similar to here:
1066 http://www.python.org/dev/peps/pep-0257/
1067 """
1068 # Get the data out of the node...
1069 notice = node.childNodes[0].data
1070
1071 # Figure out minimum indentation, skipping the first line (the same line
1072 # as the tag)...
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301073 minIndent = sys.maxsize
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001074 lines = notice.splitlines()
1075 for line in lines[1:]:
1076 lstrippedLine = line.lstrip()
1077 if lstrippedLine:
1078 indent = len(line) - len(lstrippedLine)
1079 minIndent = min(indent, minIndent)
1080
1081 # Strip leading / trailing blank lines and also indentation.
1082 cleanLines = [lines[0].strip()]
1083 for line in lines[1:]:
1084 cleanLines.append(line[minIndent:].rstrip())
1085
1086 # Clear completely blank lines from front and back...
1087 while cleanLines and not cleanLines[0]:
1088 del cleanLines[0]
1089 while cleanLines and not cleanLines[-1]:
1090 del cleanLines[-1]
1091
1092 return '\n'.join(cleanLines)
1093
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001094 def _JoinName(self, parent_name, name):
1095 return os.path.join(parent_name, name)
1096
1097 def _UnjoinName(self, parent_name, name):
1098 return os.path.relpath(name, parent_name)
1099
David Pursehousee5913ae2020-02-12 13:56:59 +09001100 def _ParseProject(self, node, parent=None, **extra_proj_attrs):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001101 """
1102 reads a element from the manifest file
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -07001103 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001104 name = self._reqatt(node, 'name')
Mike Frysingera29424e2021-02-25 21:53:49 -05001105 msg = self._CheckLocalPath(name, dir_ok=True)
1106 if msg:
1107 raise ManifestInvalidPathError(
1108 ' invalid "name": %s: %s' % (name, msg))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001109 if parent:
1110 name = self._JoinName(parent.name, name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001111
1112 remote = self._get_remote(node)
1113 if remote is None:
1114 remote = self._default.remote
1115 if remote is None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301116 raise ManifestParseError("no remote for project %s within %s" %
David Pursehouseabdf7502020-02-12 14:58:39 +09001117 (name, self.manifestFile))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001118
Anthony King36ea2fb2014-05-06 11:54:01 +01001119 revisionExpr = node.getAttribute('revision') or remote.revision
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001120 if not revisionExpr:
1121 revisionExpr = self._default.revisionExpr
1122 if not revisionExpr:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301123 raise ManifestParseError("no revision for project %s within %s" %
David Pursehouseabdf7502020-02-12 14:58:39 +09001124 (name, self.manifestFile))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001125
1126 path = node.getAttribute('path')
1127 if not path:
1128 path = name
Mike Frysingera29424e2021-02-25 21:53:49 -05001129 else:
Mike Frysinger0458faa2021-03-10 23:35:44 -05001130 # NB: The "." project is handled specially in Project.Sync_LocalHalf.
1131 msg = self._CheckLocalPath(path, dir_ok=True, cwd_dot_ok=True)
Mike Frysingera29424e2021-02-25 21:53:49 -05001132 if msg:
1133 raise ManifestInvalidPathError(
1134 ' invalid "path": %s: %s' % (path, msg))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001135
Mike Frysingerbb8ee7f2020-02-22 05:30:12 -05001136 rebase = XmlBool(node, 'rebase', True)
1137 sync_c = XmlBool(node, 'sync-c', False)
1138 sync_s = XmlBool(node, 'sync-s', self._default.sync_s)
1139 sync_tags = XmlBool(node, 'sync-tags', self._default.sync_tags)
Mike Pontillod3153822012-02-28 11:53:24 -08001140
Mike Frysingerbb8ee7f2020-02-22 05:30:12 -05001141 clone_depth = XmlInt(node, 'clone-depth')
1142 if clone_depth is not None and clone_depth <= 0:
1143 raise ManifestParseError('%s: clone-depth must be greater than 0, not "%s"' %
1144 (self.manifestFile, clone_depth))
David Pursehouseede7f122012-11-27 22:25:30 +09001145
Bryan Jacobsf609f912013-05-06 13:36:24 -04001146 dest_branch = node.getAttribute('dest-branch') or self._default.destBranchExpr
1147
Nasser Grainawida403412018-05-04 12:53:29 -06001148 upstream = node.getAttribute('upstream') or self._default.upstreamExpr
Brian Harring14a66742012-09-28 20:21:57 -07001149
Conley Owens971de8e2012-04-16 10:36:08 -07001150 groups = ''
1151 if node.hasAttribute('groups'):
1152 groups = node.getAttribute('groups')
Mike Frysinger51e39d52020-12-04 05:32:06 -05001153 groups = self._ParseList(groups)
Brian Harring7da13142012-06-15 02:24:20 -07001154
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001155 if parent is None:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05001156 relpath, worktree, gitdir, objdir, use_git_worktrees = \
1157 self.GetProjectPaths(name, path)
Shawn O. Pearcecd81dd62012-10-26 12:18:00 -07001158 else:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05001159 use_git_worktrees = False
David James8d201162013-10-11 17:03:19 -07001160 relpath, worktree, gitdir, objdir = \
1161 self.GetSubprojectPaths(parent, name, path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001162
1163 default_groups = ['all', 'name:%s' % name, 'path:%s' % relpath]
1164 groups.extend(set(default_groups).difference(groups))
Shawn O. Pearcecd81dd62012-10-26 12:18:00 -07001165
Scott Fandb83b1b2013-02-28 09:34:14 +08001166 if self.IsMirror and node.hasAttribute('force-path'):
Mike Frysingerbb8ee7f2020-02-22 05:30:12 -05001167 if XmlBool(node, 'force-path', False):
Scott Fandb83b1b2013-02-28 09:34:14 +08001168 gitdir = os.path.join(self.topdir, '%s.git' % path)
1169
David Pursehousee5913ae2020-02-12 13:56:59 +09001170 project = Project(manifest=self,
1171 name=name,
1172 remote=remote.ToRemoteSpec(name),
1173 gitdir=gitdir,
1174 objdir=objdir,
1175 worktree=worktree,
1176 relpath=relpath,
1177 revisionExpr=revisionExpr,
1178 revisionId=None,
1179 rebase=rebase,
1180 groups=groups,
1181 sync_c=sync_c,
1182 sync_s=sync_s,
1183 sync_tags=sync_tags,
1184 clone_depth=clone_depth,
1185 upstream=upstream,
1186 parent=parent,
1187 dest_branch=dest_branch,
Mike Frysinger979d5bd2020-02-09 02:28:34 -05001188 use_git_worktrees=use_git_worktrees,
Simran Basib9a1b732015-08-20 12:19:28 -07001189 **extra_proj_attrs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001190
1191 for n in node.childNodes:
Shawn O. Pearce242b5262009-05-19 13:00:29 -07001192 if n.nodeName == 'copyfile':
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001193 self._ParseCopyFile(project, n)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001194 if n.nodeName == 'linkfile':
1195 self._ParseLinkFile(project, n)
James W. Mills24c13082012-04-12 15:04:13 -05001196 if n.nodeName == 'annotation':
1197 self._ParseAnnotation(project, n)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001198 if n.nodeName == 'project':
David Pursehousee5913ae2020-02-12 13:56:59 +09001199 project.subprojects.append(self._ParseProject(n, parent=project))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001200
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001201 return project
1202
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001203 def GetProjectPaths(self, name, path):
Mike Frysingercebf2272020-05-26 01:02:29 -04001204 # The manifest entries might have trailing slashes. Normalize them to avoid
1205 # unexpected filesystem behavior since we do string concatenation below.
1206 path = path.rstrip('/')
1207 name = name.rstrip('/')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05001208 use_git_worktrees = False
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001209 relpath = path
1210 if self.IsMirror:
1211 worktree = None
1212 gitdir = os.path.join(self.topdir, '%s.git' % name)
David James8d201162013-10-11 17:03:19 -07001213 objdir = gitdir
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001214 else:
1215 worktree = os.path.join(self.topdir, path).replace('\\', '/')
1216 gitdir = os.path.join(self.repodir, 'projects', '%s.git' % path)
Mike Frysinger979d5bd2020-02-09 02:28:34 -05001217 # We allow people to mix git worktrees & non-git worktrees for now.
1218 # This allows for in situ migration of repo clients.
1219 if os.path.exists(gitdir) or not self.UseGitWorktrees:
1220 objdir = os.path.join(self.repodir, 'project-objects', '%s.git' % name)
1221 else:
1222 use_git_worktrees = True
1223 gitdir = os.path.join(self.repodir, 'worktrees', '%s.git' % name)
1224 objdir = gitdir
1225 return relpath, worktree, gitdir, objdir, use_git_worktrees
David James8d201162013-10-11 17:03:19 -07001226
1227 def GetProjectsWithName(self, name):
1228 return self._projects.get(name, [])
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001229
1230 def GetSubprojectName(self, parent, submodule_path):
1231 return os.path.join(parent.name, submodule_path)
1232
1233 def _JoinRelpath(self, parent_relpath, relpath):
1234 return os.path.join(parent_relpath, relpath)
1235
1236 def _UnjoinRelpath(self, parent_relpath, relpath):
1237 return os.path.relpath(relpath, parent_relpath)
1238
David James8d201162013-10-11 17:03:19 -07001239 def GetSubprojectPaths(self, parent, name, path):
Mike Frysingercebf2272020-05-26 01:02:29 -04001240 # The manifest entries might have trailing slashes. Normalize them to avoid
1241 # unexpected filesystem behavior since we do string concatenation below.
1242 path = path.rstrip('/')
1243 name = name.rstrip('/')
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001244 relpath = self._JoinRelpath(parent.relpath, path)
1245 gitdir = os.path.join(parent.gitdir, 'subprojects', '%s.git' % path)
David James8d201162013-10-11 17:03:19 -07001246 objdir = os.path.join(parent.gitdir, 'subproject-objects', '%s.git' % name)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001247 if self.IsMirror:
1248 worktree = None
1249 else:
1250 worktree = os.path.join(parent.worktree, path).replace('\\', '/')
David James8d201162013-10-11 17:03:19 -07001251 return relpath, worktree, gitdir, objdir
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001252
Mike Frysinger04122b72019-07-31 23:32:58 -04001253 @staticmethod
Mike Frysingera00c5f42021-02-25 18:26:31 -05001254 def _CheckLocalPath(path, dir_ok=False, cwd_dot_ok=False):
1255 """Verify |path| is reasonable for use in filesystem paths.
1256
Mike Frysingera29424e2021-02-25 21:53:49 -05001257 Used with & & elements.
Mike Frysingera00c5f42021-02-25 18:26:31 -05001258
1259 This only validates the |path| in isolation: it does not check against the
1260 current filesystem state. Thus it is suitable as a first-past in a parser.
1261
1262 It enforces a number of constraints:
1263 * No empty paths.
1264 * No "~" in paths.
1265 * No Unicode codepoints that filesystems might elide when normalizing.
1266 * No relative path components like "." or "..".
1267 * No absolute paths.
1268 * No ".git" or ".repo*" path components.
1269
1270 Args:
1271 path: The path name to validate.
1272 dir_ok: Whether |path| may force a directory (e.g. end in a /).
1273 cwd_dot_ok: Whether |path| may be just ".".
1274
1275 Returns:
1276 None if |path| is OK, a failure message otherwise.
1277 """
1278 if not path:
1279 return 'empty paths not allowed'
1280
Mike Frysinger04122b72019-07-31 23:32:58 -04001281 if '~' in path:
1282 return '~ not allowed (due to 8.3 filenames on Windows filesystems)'
1283
Mike Frysingerf69c7ee2021-04-29 23:15:31 -04001284 path_codepoints = set(path)
1285
Mike Frysinger04122b72019-07-31 23:32:58 -04001286 # Some filesystems (like Apple's HFS+) try to normalize Unicode codepoints
1287 # which means there are alternative names for ".git". Reject paths with
1288 # these in it as there shouldn't be any reasonable need for them here.
1289 # The set of codepoints here was cribbed from jgit's implementation:
1290 # https://eclipse.googlesource.com/jgit/jgit/+/9110037e3e9461ff4dac22fee84ef3694ed57648/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java#884
1291 BAD_CODEPOINTS = {
1292 u'\u200C', # ZERO WIDTH NON-JOINER
1293 u'\u200D', # ZERO WIDTH JOINER
1294 u'\u200E', # LEFT-TO-RIGHT MARK
1295 u'\u200F', # RIGHT-TO-LEFT MARK
1296 u'\u202A', # LEFT-TO-RIGHT EMBEDDING
1297 u'\u202B', # RIGHT-TO-LEFT EMBEDDING
1298 u'\u202C', # POP DIRECTIONAL FORMATTING
1299 u'\u202D', # LEFT-TO-RIGHT OVERRIDE
1300 u'\u202E', # RIGHT-TO-LEFT OVERRIDE
1301 u'\u206A', # INHIBIT SYMMETRIC SWAPPING
1302 u'\u206B', # ACTIVATE SYMMETRIC SWAPPING
1303 u'\u206C', # INHIBIT ARABIC FORM SHAPING
1304 u'\u206D', # ACTIVATE ARABIC FORM SHAPING
1305 u'\u206E', # NATIONAL DIGIT SHAPES
1306 u'\u206F', # NOMINAL DIGIT SHAPES
1307 u'\uFEFF', # ZERO WIDTH NO-BREAK SPACE
1308 }
Mike Frysingerf69c7ee2021-04-29 23:15:31 -04001309 if BAD_CODEPOINTS & path_codepoints:
Mike Frysinger04122b72019-07-31 23:32:58 -04001310 # This message is more expansive than reality, but should be fine.
1311 return 'Unicode combining characters not allowed'
1312
Mike Frysingerf69c7ee2021-04-29 23:15:31 -04001313 # Reject newlines as there shouldn't be any legitmate use for them, they'll
1314 # be confusing to users, and they can easily break tools that expect to be
1315 # able to iterate over newline delimited lists. This even applies to our
1316 # own code like .repo/project.list.
1317 if {'\r', '\n'} & path_codepoints:
1318 return 'Newlines not allowed'
1319
Mike Frysinger04122b72019-07-31 23:32:58 -04001320 # Assume paths might be used on case-insensitive filesystems.
1321 path = path.lower()
1322
Mike Frysingerd9254592020-02-19 22:36:26 -05001323 # Split up the path by its components. We can't use os.path.sep exclusively
1324 # as some platforms (like Windows) will convert / to \ and that bypasses all
1325 # our constructed logic here. Especially since manifest authors only use
1326 # / in their paths.
1327 resep = re.compile(r'[/%s]' % re.escape(os.path.sep))
Mike Frysinger0458faa2021-03-10 23:35:44 -05001328 # Strip off trailing slashes as those only produce '' elements, and we use
1329 # parts to look for individual bad components.
1330 parts = resep.split(path.rstrip('/'))
Mike Frysingerd9254592020-02-19 22:36:26 -05001331
Mike Frysingerae625412020-02-10 17:10:03 -05001332 # Some people use src="." to create stable links to projects. Lets allow
1333 # that but reject all other uses of "." to keep things simple.
Mike Frysingera00c5f42021-02-25 18:26:31 -05001334 if not cwd_dot_ok or parts != ['.']:
Mike Frysingerae625412020-02-10 17:10:03 -05001335 for part in set(parts):
1336 if part in {'.', '..', '.git'} or part.startswith('.repo'):
1337 return 'bad component: %s' % (part,)
Mike Frysinger04122b72019-07-31 23:32:58 -04001338
Mike Frysingera00c5f42021-02-25 18:26:31 -05001339 if not dir_ok and resep.match(path[-1]):
Mike Frysinger04122b72019-07-31 23:32:58 -04001340 return 'dirs not allowed'
1341
Mike Frysingerd9254592020-02-19 22:36:26 -05001342 # NB: The two abspath checks here are to handle platforms with multiple
1343 # filesystem path styles (e.g. Windows).
Mike Frysinger04122b72019-07-31 23:32:58 -04001344 norm = os.path.normpath(path)
Mike Frysingerd9254592020-02-19 22:36:26 -05001345 if (norm == '..' or
1346 (len(norm) >= 3 and norm.startswith('..') and resep.match(norm[0])) or
1347 os.path.isabs(norm) or
1348 norm.startswith('/')):
Mike Frysinger04122b72019-07-31 23:32:58 -04001349 return 'path cannot be outside'
1350
1351 @classmethod
1352 def _ValidateFilePaths(cls, element, src, dest):
1353 """Verify |src| & |dest| are reasonable for & .
1354
1355 We verify the path independent of any filesystem state as we won't have a
1356 checkout available to compare to. i.e. This is for parsing validation
1357 purposes only.
1358
1359 We'll do full/live sanity checking before we do the actual filesystem
1360 modifications in _CopyFile/_LinkFile/etc...
1361 """
1362 # |dest| is the file we write to or symlink we create.
1363 # It is relative to the top of the repo client checkout.
1364 msg = cls._CheckLocalPath(dest)
1365 if msg:
1366 raise ManifestInvalidPathError(
1367 '<%s> invalid "dest": %s: %s' % (element, dest, msg))
1368
1369 # |src| is the file we read from or path we point to for symlinks.
1370 # It is relative to the top of the git project checkout.
Mike Frysingera00c5f42021-02-25 18:26:31 -05001371 is_linkfile = element == 'linkfile'
1372 msg = cls._CheckLocalPath(src, dir_ok=is_linkfile, cwd_dot_ok=is_linkfile)
Mike Frysinger04122b72019-07-31 23:32:58 -04001373 if msg:
1374 raise ManifestInvalidPathError(
1375 '<%s> invalid "src": %s: %s' % (element, src, msg))
1376
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001377 def _ParseCopyFile(self, project, node):
1378 src = self._reqatt(node, 'src')
1379 dest = self._reqatt(node, 'dest')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001380 if not self.IsMirror:
1381 # src is project relative;
Mike Frysinger04122b72019-07-31 23:32:58 -04001382 # dest is relative to the top of the tree.
1383 # We only validate paths if we actually plan to process them.
1384 self._ValidateFilePaths('copyfile', src, dest)
Mike Frysingere6a202f2019-08-02 15:57:57 -04001385 project.AddCopyFile(src, dest, self.topdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001386
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001387 def _ParseLinkFile(self, project, node):
1388 src = self._reqatt(node, 'src')
1389 dest = self._reqatt(node, 'dest')
1390 if not self.IsMirror:
1391 # src is project relative;
Mike Frysinger04122b72019-07-31 23:32:58 -04001392 # dest is relative to the top of the tree.
1393 # We only validate paths if we actually plan to process them.
1394 self._ValidateFilePaths('linkfile', src, dest)
Mike Frysingere6a202f2019-08-02 15:57:57 -04001395 project.AddLinkFile(src, dest, self.topdir)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001396
Jack Neus6ea0cae2021-07-20 20:52:33 +00001397 def _ParseAnnotation(self, element, node):
James W. Mills24c13082012-04-12 15:04:13 -05001398 name = self._reqatt(node, 'name')
1399 value = self._reqatt(node, 'value')
1400 try:
1401 keep = self._reqatt(node, 'keep').lower()
1402 except ManifestParseError:
1403 keep = "true"
1404 if keep != "true" and keep != "false":
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301405 raise ManifestParseError('optional "keep" attribute must be '
David Pursehouseabdf7502020-02-12 14:58:39 +09001406 '"true" or "false"')
Jack Neus6ea0cae2021-07-20 20:52:33 +00001407 element.AddAnnotation(name, value, keep)
James W. Mills24c13082012-04-12 15:04:13 -05001408
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001409 def _get_remote(self, node):
1410 name = node.getAttribute('remote')
1411 if not name:
1412 return None
1413
1414 v = self._remotes.get(name)
1415 if not v:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301416 raise ManifestParseError("remote %s not defined in %s" %
David Pursehouseabdf7502020-02-12 14:58:39 +09001417 (name, self.manifestFile))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001418 return v
1419
1420 def _reqatt(self, node, attname):
1421 """
1422 reads a required attribute from the node.
1423 """
1424 v = node.getAttribute(attname)
1425 if not v:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301426 raise ManifestParseError("no %s in <%s> within %s" %
David Pursehouseabdf7502020-02-12 14:58:39 +09001427 (attname, node.nodeName, self.manifestFile))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001428 return v
Julien Camperguedd654222014-01-09 16:21:37 +01001429
1430 def projectsDiff(self, manifest):
1431 """return the projects differences between two manifests.
1432
1433 The diff will be from self to given manifest.
1434
1435 """
1436 fromProjects = self.paths
1437 toProjects = manifest.paths
1438
Anthony King7446c592014-05-06 09:19:39 +01001439 fromKeys = sorted(fromProjects.keys())
1440 toKeys = sorted(toProjects.keys())
Julien Camperguedd654222014-01-09 16:21:37 +01001441
1442 diff = {'added': [], 'removed': [], 'changed': [], 'unreachable': []}
1443
1444 for proj in fromKeys:
David Pursehouseeeff3532020-02-12 11:24:10 +09001445 if proj not in toKeys:
Julien Camperguedd654222014-01-09 16:21:37 +01001446 diff['removed'].append(fromProjects[proj])
1447 else:
1448 fromProj = fromProjects[proj]
1449 toProj = toProjects[proj]
1450 try:
1451 fromRevId = fromProj.GetCommitRevisionId()
1452 toRevId = toProj.GetCommitRevisionId()
1453 except ManifestInvalidRevisionError:
1454 diff['unreachable'].append((fromProj, toProj))
1455 else:
1456 if fromRevId != toRevId:
1457 diff['changed'].append((fromProj, toProj))
1458 toKeys.remove(proj)
1459
1460 for proj in toKeys:
1461 diff['added'].append(toProjects[proj])
1462
1463 return diff
Simran Basib9a1b732015-08-20 12:19:28 -07001464
1465
1466class GitcManifest(XmlManifest):
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -04001467 """Parser for GitC (git-in-the-cloud) manifests."""
Simran Basib9a1b732015-08-20 12:19:28 -07001468
David Pursehousee5913ae2020-02-12 13:56:59 +09001469 def _ParseProject(self, node, parent=None):
Simran Basib9a1b732015-08-20 12:19:28 -07001470 """Override _ParseProject and add support for GITC specific attributes."""
Mike Frysinger5d9c4972021-02-19 13:34:09 -05001471 return super()._ParseProject(
Simran Basib9a1b732015-08-20 12:19:28 -07001472 node, parent=parent, old_revision=node.getAttribute('old-revision'))
1473
1474 def _output_manifest_project_extras(self, p, e):
1475 """Output GITC Specific Project attributes"""
1476 if p.old_revision:
Stefan Beller66851062016-06-17 16:40:08 -07001477 e.setAttribute('old-revision', str(p.old_revision))
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -04001478
1479
1480class RepoClient(XmlManifest):
1481 """Manages a repo client checkout."""
1482
1483 def __init__(self, repodir, manifest_file=None):
1484 self.isGitcClient = False
1485
1486 if os.path.exists(os.path.join(repodir, LOCAL_MANIFEST_NAME)):
1487 print('error: %s is not supported; put local manifests in `%s` instead' %
1488 (LOCAL_MANIFEST_NAME, os.path.join(repodir, LOCAL_MANIFESTS_DIR_NAME)),
1489 file=sys.stderr)
1490 sys.exit(1)
1491
1492 if manifest_file is None:
1493 manifest_file = os.path.join(repodir, MANIFEST_FILE_NAME)
1494 local_manifests = os.path.abspath(os.path.join(repodir, LOCAL_MANIFESTS_DIR_NAME))
Mike Frysinger5d9c4972021-02-19 13:34:09 -05001495 super().__init__(repodir, manifest_file, local_manifests)
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -04001496
1497 # TODO: Completely separate manifest logic out of the client.
1498 self.manifest = self
1499
1500
1501class GitcClient(RepoClient, GitcManifest):
1502 """Manages a GitC client checkout."""
1503
1504 def __init__(self, repodir, gitc_client_name):
1505 """Initialize the GitcManifest object."""
1506 self.gitc_client_name = gitc_client_name
1507 self.gitc_client_dir = os.path.join(gitc_utils.get_gitc_manifest_dir(),
1508 gitc_client_name)
1509
Mike Frysinger5d9c4972021-02-19 13:34:09 -05001510 super().__init__(repodir, os.path.join(self.gitc_client_dir, '.manifest'))
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -04001511 self.isGitcClient = True