blob: cac271cd999007e8c2e52571ee1746d5619d5bd7 [file] [log] [blame]
Shawn O. Pearce0125ae22009-07-03 18:05:23 -07001#
2# Copyright (C) 2009 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 sys
17import os
18import shutil
19
20from error import GitError
21from error import ManifestParseError
22from git_command import GitCommand
23from git_config import GitConfig
24from git_config import IsId
25from manifest import Manifest
26from progress import Progress
27from project import RemoteSpec
28from project import Project
29from project import MetaProject
30from project import R_HEADS
31from project import HEAD
32from project import _lwrite
33
34import manifest_xml
35
36GITLINK = '160000'
37
38def _rmdir(dir, top):
39 while dir != top:
40 try:
41 os.rmdir(dir)
42 except OSError:
43 break
44 dir = os.path.dirname(dir)
45
46def _rmref(gitdir, ref):
47 os.remove(os.path.join(gitdir, ref))
48 log = os.path.join(gitdir, 'logs', ref)
49 if os.path.exists(log):
50 os.remove(log)
51 _rmdir(os.path.dirname(log), gitdir)
52
53def _has_gitmodules(d):
54 return os.path.exists(os.path.join(d, '.gitmodules'))
55
56class SubmoduleManifest(Manifest):
57 """manifest from .gitmodules file"""
58
59 @classmethod
60 def Is(cls, repodir):
61 return _has_gitmodules(os.path.dirname(repodir)) \
62 or _has_gitmodules(os.path.join(repodir, 'manifest')) \
63 or _has_gitmodules(os.path.join(repodir, 'manifests'))
64
65 @classmethod
66 def IsBare(cls, p):
67 try:
68 p.bare_git.cat_file('-e', '%s:.gitmodules' % p.GetRevisionId())
69 except GitError:
70 return False
71 return True
72
73 def __init__(self, repodir):
74 Manifest.__init__(self, repodir)
75
76 gitdir = os.path.join(repodir, 'manifest.git')
77 config = GitConfig.ForRepository(gitdir = gitdir)
78
79 if config.GetBoolean('repo.mirror'):
80 worktree = os.path.join(repodir, 'manifest')
81 relpath = None
82 else:
83 worktree = self.topdir
84 relpath = '.'
85
86 self.manifestProject = MetaProject(self, '__manifest__',
87 gitdir = gitdir,
88 worktree = worktree,
89 relpath = relpath)
90 self._modules = GitConfig(os.path.join(worktree, '.gitmodules'),
91 pickleFile = os.path.join(
92 repodir, '.repopickle_gitmodules'
93 ))
94 self._review = GitConfig(os.path.join(worktree, '.review'),
95 pickleFile = os.path.join(
96 repodir, '.repopickle_review'
97 ))
98 self._Unload()
99
100 @property
101 def projects(self):
102 self._Load()
103 return self._projects
104
Shawn O. Pearce13f3da52010-12-07 10:31:19 -0800105 @property
106 def notice(self):
107 return self._modules.GetString('repo.notice')
108
Shawn O. Pearce0125ae22009-07-03 18:05:23 -0700109 def InitBranch(self):
110 m = self.manifestProject
111 if m.CurrentBranch is None:
112 b = m.revisionExpr
113 if b.startswith(R_HEADS):
114 b = b[len(R_HEADS):]
115 return m.StartBranch(b)
116 return True
117
118 def SetMRefs(self, project):
119 if project.revisionId is None:
120 # Special project, e.g. the manifest or repo executable.
121 #
122 return
123
124 ref = 'refs/remotes/m'
125 cur = project.bare_ref.get(ref)
126 exp = project.revisionId
127 if cur != exp:
128 msg = 'manifest set to %s' % exp
129 project.bare_git.UpdateRef(ref, exp, message = msg, detach = True)
130
131 ref = 'refs/remotes/m-revision'
132 cur = project.bare_ref.symref(ref)
133 exp = project.revisionExpr
134 if exp is None:
135 if cur:
136 _rmref(project.gitdir, ref)
137 elif cur != exp:
138 remote = project.GetRemote(project.remote.name)
139 dst = remote.ToLocal(exp)
140 msg = 'manifest set to %s (%s)' % (exp, dst)
141 project.bare_git.symbolic_ref('-m', msg, ref, dst)
142
143 def Upgrade_Local(self, old):
144 if isinstance(old, manifest_xml.XmlManifest):
145 self.FromXml_Local_1(old, checkout=True)
146 self.FromXml_Local_2(old)
147 else:
148 raise ManifestParseError, 'cannot upgrade manifest'
149
150 def FromXml_Local_1(self, old, checkout):
151 os.rename(old.manifestProject.gitdir,
152 os.path.join(old.repodir, 'manifest.git'))
153
154 oldmp = old.manifestProject
155 oldBranch = oldmp.CurrentBranch
156 b = oldmp.GetBranch(oldBranch).merge
157 if not b:
158 raise ManifestParseError, 'cannot upgrade manifest'
159 if b.startswith(R_HEADS):
160 b = b[len(R_HEADS):]
161
162 newmp = self.manifestProject
163 self._CleanOldMRefs(newmp)
164 if oldBranch != b:
165 newmp.bare_git.branch('-m', oldBranch, b)
166 newmp.config.ClearCache()
167
168 old_remote = newmp.GetBranch(b).remote.name
169 act_remote = self._GuessRemoteName(old)
170 if old_remote != act_remote:
171 newmp.bare_git.remote('rename', old_remote, act_remote)
172 newmp.config.ClearCache()
173 newmp.remote.name = act_remote
174 print >>sys.stderr, "Assuming remote named '%s'" % act_remote
175
176 if checkout:
177 for p in old.projects.values():
178 for c in p.copyfiles:
179 if os.path.exists(c.abs_dest):
180 os.remove(c.abs_dest)
181 newmp._InitWorkTree()
182 else:
183 newmp._LinkWorkTree()
184
185 _lwrite(os.path.join(newmp.worktree,'.git',HEAD),
186 'ref: refs/heads/%s\n' % b)
187
188 def _GuessRemoteName(self, old):
189 used = {}
190 for p in old.projects.values():
191 n = p.remote.name
192 used[n] = used.get(n, 0) + 1
193
194 remote_name = 'origin'
195 remote_used = 0
196 for n in used.keys():
197 if remote_used < used[n]:
198 remote_used = used[n]
199 remote_name = n
200 return remote_name
201
202 def FromXml_Local_2(self, old):
203 shutil.rmtree(old.manifestProject.worktree)
204 os.remove(old._manifestFile)
205
206 my_remote = self._Remote().name
207 new_base = os.path.join(self.repodir, 'projects')
208 old_base = os.path.join(self.repodir, 'projects.old')
209 os.rename(new_base, old_base)
210 os.makedirs(new_base)
211
212 info = []
213 pm = Progress('Converting projects', len(self.projects))
214 for p in self.projects.values():
215 pm.update()
216
217 old_p = old.projects.get(p.name)
218 old_gitdir = os.path.join(old_base, '%s.git' % p.relpath)
219 if not os.path.isdir(old_gitdir):
220 continue
221
222 parent = os.path.dirname(p.gitdir)
223 if not os.path.isdir(parent):
224 os.makedirs(parent)
225 os.rename(old_gitdir, p.gitdir)
226 _rmdir(os.path.dirname(old_gitdir), self.repodir)
227
228 if not os.path.isdir(p.worktree):
229 os.makedirs(p.worktree)
230
231 if os.path.isdir(os.path.join(p.worktree, '.git')):
232 p._LinkWorkTree(relink=True)
233
234 self._CleanOldMRefs(p)
235 if old_p and old_p.remote.name != my_remote:
236 info.append("%s/: renamed remote '%s' to '%s'" \
237 % (p.relpath, old_p.remote.name, my_remote))
238 p.bare_git.remote('rename', old_p.remote.name, my_remote)
239 p.config.ClearCache()
240
241 self.SetMRefs(p)
242 pm.end()
243 for i in info:
244 print >>sys.stderr, i
245
246 def _CleanOldMRefs(self, p):
247 all_refs = p._allrefs
248 for ref in all_refs.keys():
249 if ref.startswith(manifest_xml.R_M):
250 if p.bare_ref.symref(ref) != '':
251 _rmref(p.gitdir, ref)
252 else:
253 p.bare_git.DeleteRef(ref, all_refs[ref])
254
255 def FromXml_Definition(self, old):
256 """Convert another manifest representation to this one.
257 """
258 mp = self.manifestProject
259 gm = self._modules
260 gr = self._review
261
262 fd = open(os.path.join(mp.worktree, '.gitignore'), 'ab')
263 fd.write('/.repo\n')
264 fd.close()
265
266 sort_projects = list(old.projects.keys())
267 sort_projects.sort()
268
269 b = mp.GetBranch(mp.CurrentBranch).merge
270 if b.startswith(R_HEADS):
271 b = b[len(R_HEADS):]
272
Shawn O. Pearce13f3da52010-12-07 10:31:19 -0800273 if old.notice:
274 gm.SetString('repo.notice', old.notice)
275
Shawn O. Pearce0125ae22009-07-03 18:05:23 -0700276 info = []
277 pm = Progress('Converting manifest', len(sort_projects))
278 for p in sort_projects:
279 pm.update()
280 p = old.projects[p]
281
282 gm.SetString('submodule.%s.path' % p.name, p.relpath)
283 gm.SetString('submodule.%s.url' % p.name, p.remote.url)
284
285 if gr.GetString('review.url') is None:
286 gr.SetString('review.url', p.remote.review)
287 elif gr.GetString('review.url') != p.remote.review:
288 gr.SetString('review.%s.url' % p.name, p.remote.review)
289
290 r = p.revisionExpr
291 if r and not IsId(r):
292 if r.startswith(R_HEADS):
293 r = r[len(R_HEADS):]
294 if r == b:
295 r = '.'
296 gm.SetString('submodule.%s.revision' % p.name, r)
297
298 for c in p.copyfiles:
299 info.append('Moved %s out of %s' % (c.src, p.relpath))
300 c._Copy()
301 p.work_git.rm(c.src)
302 mp.work_git.add(c.dest)
303
304 self.SetRevisionId(p.relpath, p.GetRevisionId())
305 mp.work_git.add('.gitignore', '.gitmodules', '.review')
306 pm.end()
307 for i in info:
308 print >>sys.stderr, i
309
310 def _Unload(self):
311 self._loaded = False
312 self._projects = {}
313 self._revisionIds = None
314 self.branch = None
315
316 def _Load(self):
317 if not self._loaded:
318 f = os.path.join(self.repodir, manifest_xml.LOCAL_MANIFEST_NAME)
319 if os.path.exists(f):
320 print >>sys.stderr, 'warning: ignoring %s' % f
321
322 m = self.manifestProject
323 b = m.CurrentBranch
324 if not b:
325 raise ManifestParseError, 'manifest cannot be on detached HEAD'
326 b = m.GetBranch(b).merge
327 if b.startswith(R_HEADS):
328 b = b[len(R_HEADS):]
329 self.branch = b
330 m.remote.name = self._Remote().name
331
332 self._ParseModules()
333
334 if self.IsMirror:
335 self._AddMetaProjectMirror(self.repoProject)
336 self._AddMetaProjectMirror(self.manifestProject)
337
338 self._loaded = True
339
340 def _ParseModules(self):
341 byPath = dict()
342 for name in self._modules.GetSubSections('submodule'):
343 p = self._ParseProject(name)
344 if self._projects.get(p.name):
345 raise ManifestParseError, 'duplicate project "%s"' % p.name
346 if byPath.get(p.relpath):
347 raise ManifestParseError, 'duplicate path "%s"' % p.relpath
348 self._projects[p.name] = p
349 byPath[p.relpath] = p
350
351 for relpath in self._allRevisionIds.keys():
352 if relpath not in byPath:
353 raise ManifestParseError, \
354 'project "%s" not in .gitmodules' \
355 % relpath
356
357 def _Remote(self):
358 m = self.manifestProject
359 b = m.GetBranch(m.CurrentBranch)
360 return b.remote
361
362 def _ResolveUrl(self, url):
363 if url.startswith('./') or url.startswith('../'):
364 base = self._Remote().url
365 try:
366 base = base[:base.rindex('/')+1]
367 except ValueError:
368 base = base[:base.rindex(':')+1]
369 if url.startswith('./'):
370 url = url[2:]
371 while '/' in base and url.startswith('../'):
372 base = base[:base.rindex('/')+1]
373 url = url[3:]
374 return base + url
375 return url
376
377 def _GetRevisionId(self, path):
378 return self._allRevisionIds.get(path)
379
380 @property
381 def _allRevisionIds(self):
382 if self._revisionIds is None:
383 a = dict()
384 p = GitCommand(self.manifestProject,
385 ['ls-files','-z','--stage'],
386 capture_stdout = True)
387 for line in p.process.stdout.read().split('\0')[:-1]:
388 l_info, l_path = line.split('\t', 2)
389 l_mode, l_id, l_stage = l_info.split(' ', 2)
390 if l_mode == GITLINK and l_stage == '0':
391 a[l_path] = l_id
392 p.Wait()
393 self._revisionIds = a
394 return self._revisionIds
395
396 def SetRevisionId(self, path, id):
397 self.manifestProject.work_git.update_index(
398 '--add','--cacheinfo', GITLINK, id, path)
399
400 def _ParseProject(self, name):
401 gm = self._modules
402 gr = self._review
403
404 path = gm.GetString('submodule.%s.path' % name)
405 if not path:
406 path = name
407
408 revId = self._GetRevisionId(path)
409 if not revId:
410 raise ManifestParseError(
411 'submodule "%s" has no revision at "%s"' \
412 % (name, path))
413
414 url = gm.GetString('submodule.%s.url' % name)
415 if not url:
416 url = name
417 url = self._ResolveUrl(url)
418
419 review = gr.GetString('review.%s.url' % name)
420 if not review:
421 review = gr.GetString('review.url')
422 if not review:
423 review = self._Remote().review
424
425 remote = RemoteSpec(self._Remote().name, url, review)
426 revExpr = gm.GetString('submodule.%s.revision' % name)
427 if revExpr == '.':
428 revExpr = self.branch
429
430 if self.IsMirror:
431 relpath = None
432 worktree = None
433 gitdir = os.path.join(self.topdir, '%s.git' % name)
434 else:
435 worktree = os.path.join(self.topdir, path)
436 gitdir = os.path.join(self.repodir, 'projects/%s.git' % name)
437
438 return Project(manifest = self,
439 name = name,
440 remote = remote,
441 gitdir = gitdir,
442 worktree = worktree,
443 relpath = path,
444 revisionExpr = revExpr,
445 revisionId = revId)
446
447 def _AddMetaProjectMirror(self, m):
448 m_url = m.GetRemote(m.remote.name).url
449 if m_url.endswith('/.git'):
450 raise ManifestParseError, 'refusing to mirror %s' % m_url
451
452 name = self._GuessMetaName(m_url)
453 if name.endswith('.git'):
454 name = name[:-4]
455
456 if name not in self._projects:
457 m.PreSync()
458 gitdir = os.path.join(self.topdir, '%s.git' % name)
459 project = Project(manifest = self,
460 name = name,
461 remote = RemoteSpec(self._Remote().name, m_url),
462 gitdir = gitdir,
463 worktree = None,
464 relpath = None,
465 revisionExpr = m.revisionExpr,
466 revisionId = None)
467 self._projects[project.name] = project
468
469 def _GuessMetaName(self, m_url):
470 parts = m_url.split('/')
471 name = parts[-1]
472 parts = parts[0:-1]
473 s = len(parts) - 1
474 while s > 0:
475 l = '/'.join(parts[0:s]) + '/'
476 r = '/'.join(parts[s:]) + '/'
477 for p in self._projects.values():
478 if p.name.startswith(r) and p.remote.url.startswith(l):
479 return r + name
480 s -= 1
481 return m_url[m_url.rindex('/') + 1:]