summaryrefslogtreecommitdiffstats
path: root/manifest_xml.py
diff options
context:
space:
mode:
authorShawn O. Pearce <sop@google.com>2009-05-18 13:19:57 -0700
committerShawn O. Pearce <sop@google.com>2009-05-29 09:31:28 -0700
commitc8a300f6397dad7db00c3654ff6e50e9519ed7c9 (patch)
tree4bd29d3d580e53965b66ed2d10e13507584fe577 /manifest_xml.py
parent1b34c9118ed86a15b0bc1094804c095dd7be33cb (diff)
downloadgit-repo-c8a300f6397dad7db00c3654ff6e50e9519ed7c9.tar.gz
Refactor Manifest to be XmlManifest
We'll soon be supporting two different manifest formats, but we can't immediately remove support for the current XML one that is in wide spread use within Android. Signed-off-by: Shawn O. Pearce <sop@google.com>
Diffstat (limited to 'manifest_xml.py')
-rw-r--r--manifest_xml.py381
1 files changed, 381 insertions, 0 deletions
diff --git a/manifest_xml.py b/manifest_xml.py
new file mode 100644
index 00000000..46976538
--- /dev/null
+++ b/manifest_xml.py
@@ -0,0 +1,381 @@
1#
2# Copyright (C) 2008 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import os
17import sys
18import xml.dom.minidom
19
20from git_config import GitConfig, IsId
21from project import Project, MetaProject, R_HEADS, HEAD
22from remote import Remote
23from error import ManifestParseError
24
25MANIFEST_FILE_NAME = 'manifest.xml'
26LOCAL_MANIFEST_NAME = 'local_manifest.xml'
27
28class _Default(object):
29 """Project defaults within the manifest."""
30
31 revision = None
32 remote = None
33
34
35class XmlManifest(object):
36 """manages the repo configuration file"""
37
38 def __init__(self, repodir):
39 self.repodir = os.path.abspath(repodir)
40 self.topdir = os.path.dirname(self.repodir)
41 self.manifestFile = os.path.join(self.repodir, MANIFEST_FILE_NAME)
42 self.globalConfig = GitConfig.ForUser()
43
44 self.repoProject = MetaProject(self, 'repo',
45 gitdir = os.path.join(repodir, 'repo/.git'),
46 worktree = os.path.join(repodir, 'repo'))
47
48 self.manifestProject = MetaProject(self, 'manifests',
49 gitdir = os.path.join(repodir, 'manifests.git'),
50 worktree = os.path.join(repodir, 'manifests'))
51
52 self._Unload()
53
54 def Link(self, name):
55 """Update the repo metadata to use a different manifest.
56 """
57 path = os.path.join(self.manifestProject.worktree, name)
58 if not os.path.isfile(path):
59 raise ManifestParseError('manifest %s not found' % name)
60
61 old = self.manifestFile
62 try:
63 self.manifestFile = path
64 self._Unload()
65 self._Load()
66 finally:
67 self.manifestFile = old
68
69 try:
70 if os.path.exists(self.manifestFile):
71 os.remove(self.manifestFile)
72 os.symlink('manifests/%s' % name, self.manifestFile)
73 except OSError, e:
74 raise ManifestParseError('cannot link manifest %s' % name)
75
76 def _RemoteToXml(self, r, doc, root):
77 e = doc.createElement('remote')
78 root.appendChild(e)
79 e.setAttribute('name', r.name)
80 e.setAttribute('fetch', r.fetchUrl)
81 if r.reviewUrl is not None:
82 e.setAttribute('review', r.reviewUrl)
83
84 def Save(self, fd, peg_rev=False):
85 """Write the current manifest out to the given file descriptor.
86 """
87 doc = xml.dom.minidom.Document()
88 root = doc.createElement('manifest')
89 doc.appendChild(root)
90
91 d = self.default
92 sort_remotes = list(self.remotes.keys())
93 sort_remotes.sort()
94
95 for r in sort_remotes:
96 self._RemoteToXml(self.remotes[r], doc, root)
97 if self.remotes:
98 root.appendChild(doc.createTextNode(''))
99
100 have_default = False
101 e = doc.createElement('default')
102 if d.remote:
103 have_default = True
104 e.setAttribute('remote', d.remote.name)
105 if d.revision:
106 have_default = True
107 e.setAttribute('revision', d.revision)
108 if have_default:
109 root.appendChild(e)
110 root.appendChild(doc.createTextNode(''))
111
112 sort_projects = list(self.projects.keys())
113 sort_projects.sort()
114
115 for p in sort_projects:
116 p = self.projects[p]
117 e = doc.createElement('project')
118 root.appendChild(e)
119 e.setAttribute('name', p.name)
120 if p.relpath != p.name:
121 e.setAttribute('path', p.relpath)
122 if not d.remote or p.remote.name != d.remote.name:
123 e.setAttribute('remote', p.remote.name)
124 if peg_rev:
125 if self.IsMirror:
126 e.setAttribute('revision',
127 p.bare_git.rev_parse(p.revision + '^0'))
128 else:
129 e.setAttribute('revision',
130 p.work_git.rev_parse(HEAD + '^0'))
131 elif not d.revision or p.revision != d.revision:
132 e.setAttribute('revision', p.revision)
133
134 for c in p.copyfiles:
135 ce = doc.createElement('copyfile')
136 ce.setAttribute('src', c.src)
137 ce.setAttribute('dest', c.dest)
138 e.appendChild(ce)
139
140 doc.writexml(fd, '', ' ', '\n', 'UTF-8')
141
142 @property
143 def projects(self):
144 self._Load()
145 return self._projects
146
147 @property
148 def remotes(self):
149 self._Load()
150 return self._remotes
151
152 @property
153 def default(self):
154 self._Load()
155 return self._default
156
157 @property
158 def IsMirror(self):
159 return self.manifestProject.config.GetBoolean('repo.mirror')
160
161 def _Unload(self):
162 self._loaded = False
163 self._projects = {}
164 self._remotes = {}
165 self._default = None
166 self.branch = None
167
168 def _Load(self):
169 if not self._loaded:
170 m = self.manifestProject
171 b = m.GetBranch(m.CurrentBranch).merge
172 if b.startswith(R_HEADS):
173 b = b[len(R_HEADS):]
174 self.branch = b
175
176 self._ParseManifest(True)
177
178 local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME)
179 if os.path.exists(local):
180 try:
181 real = self.manifestFile
182 self.manifestFile = local
183 self._ParseManifest(False)
184 finally:
185 self.manifestFile = real
186
187 if self.IsMirror:
188 self._AddMetaProjectMirror(self.repoProject)
189 self._AddMetaProjectMirror(self.manifestProject)
190
191 self._loaded = True
192
193 def _ParseManifest(self, is_root_file):
194 root = xml.dom.minidom.parse(self.manifestFile)
195 if not root or not root.childNodes:
196 raise ManifestParseError, \
197 "no root node in %s" % \
198 self.manifestFile
199
200 config = root.childNodes[0]
201 if config.nodeName != 'manifest':
202 raise ManifestParseError, \
203 "no <manifest> in %s" % \
204 self.manifestFile
205
206 for node in config.childNodes:
207 if node.nodeName == 'remove-project':
208 name = self._reqatt(node, 'name')
209 try:
210 del self._projects[name]
211 except KeyError:
212 raise ManifestParseError, \
213 'project %s not found' % \
214 (name)
215
216 for node in config.childNodes:
217 if node.nodeName == 'remote':
218 remote = self._ParseRemote(node)
219 if self._remotes.get(remote.name):
220 raise ManifestParseError, \
221 'duplicate remote %s in %s' % \
222 (remote.name, self.manifestFile)
223 self._remotes[remote.name] = remote
224
225 for node in config.childNodes:
226 if node.nodeName == 'default':
227 if self._default is not None:
228 raise ManifestParseError, \
229 'duplicate default in %s' % \
230 (self.manifestFile)
231 self._default = self._ParseDefault(node)
232 if self._default is None:
233 self._default = _Default()
234
235 for node in config.childNodes:
236 if node.nodeName == 'project':
237 project = self._ParseProject(node)
238 if self._projects.get(project.name):
239 raise ManifestParseError, \
240 'duplicate project %s in %s' % \
241 (project.name, self.manifestFile)
242 self._projects[project.name] = project
243
244 def _AddMetaProjectMirror(self, m):
245 name = None
246 m_url = m.GetRemote(m.remote.name).url
247 if m_url.endswith('/.git'):
248 raise ManifestParseError, 'refusing to mirror %s' % m_url
249
250 if self._default and self._default.remote:
251 url = self._default.remote.fetchUrl
252 if not url.endswith('/'):
253 url += '/'
254 if m_url.startswith(url):
255 remote = self._default.remote
256 name = m_url[len(url):]
257
258 if name is None:
259 s = m_url.rindex('/') + 1
260 remote = Remote('origin', fetch = m_url[:s])
261 name = m_url[s:]
262
263 if name.endswith('.git'):
264 name = name[:-4]
265
266 if name not in self._projects:
267 m.PreSync()
268 gitdir = os.path.join(self.topdir, '%s.git' % name)
269 project = Project(manifest = self,
270 name = name,
271 remote = remote,
272 gitdir = gitdir,
273 worktree = None,
274 relpath = None,
275 revision = m.revision)
276 self._projects[project.name] = project
277
278 def _ParseRemote(self, node):
279 """
280 reads a <remote> element from the manifest file
281 """
282 name = self._reqatt(node, 'name')
283 fetch = self._reqatt(node, 'fetch')
284 review = node.getAttribute('review')
285 if review == '':
286 review = None
287 return Remote(name=name, fetch=fetch, review=review)
288
289 def _ParseDefault(self, node):
290 """
291 reads a <default> element from the manifest file
292 """
293 d = _Default()
294 d.remote = self._get_remote(node)
295 d.revision = node.getAttribute('revision')
296 if d.revision == '':
297 d.revision = None
298 return d
299
300 def _ParseProject(self, node):
301 """
302 reads a <project> element from the manifest file
303 """
304 name = self._reqatt(node, 'name')
305
306 remote = self._get_remote(node)
307 if remote is None:
308 remote = self._default.remote
309 if remote is None:
310 raise ManifestParseError, \
311 "no remote for project %s within %s" % \
312 (name, self.manifestFile)
313
314 revision = node.getAttribute('revision')
315 if not revision:
316 revision = self._default.revision
317 if not revision:
318 raise ManifestParseError, \
319 "no revision for project %s within %s" % \
320 (name, self.manifestFile)
321
322 path = node.getAttribute('path')
323 if not path:
324 path = name
325 if path.startswith('/'):
326 raise ManifestParseError, \
327 "project %s path cannot be absolute in %s" % \
328 (name, self.manifestFile)
329
330 if self.IsMirror:
331 relpath = None
332 worktree = None
333 gitdir = os.path.join(self.topdir, '%s.git' % name)
334 else:
335 worktree = os.path.join(self.topdir, path)
336 gitdir = os.path.join(self.repodir, 'projects/%s.git' % path)
337
338 project = Project(manifest = self,
339 name = name,
340 remote = remote,
341 gitdir = gitdir,
342 worktree = worktree,
343 relpath = path,
344 revision = revision)
345
346 for n in node.childNodes:
347 if n.nodeName == 'copyfile':
348 self._ParseCopyFile(project, n)
349
350 return project
351
352 def _ParseCopyFile(self, project, node):
353 src = self._reqatt(node, 'src')
354 dest = self._reqatt(node, 'dest')
355 if not self.IsMirror:
356 # src is project relative;
357 # dest is relative to the top of the tree
358 project.AddCopyFile(src, dest, os.path.join(self.topdir, dest))
359
360 def _get_remote(self, node):
361 name = node.getAttribute('remote')
362 if not name:
363 return None
364
365 v = self._remotes.get(name)
366 if not v:
367 raise ManifestParseError, \
368 "remote %s not defined in %s" % \
369 (name, self.manifestFile)
370 return v
371
372 def _reqatt(self, node, attname):
373 """
374 reads a required attribute from the node.
375 """
376 v = node.getAttribute(attname)
377 if not v:
378 raise ManifestParseError, \
379 "no %s in <%s> within %s" % \
380 (attname, node.nodeName, self.manifestFile)
381 return v