|
|
@@ -0,0 +1,474 @@
|
|
|
+#
|
|
|
+# Copyright (C) 2009 The Android Open Source Project
|
|
|
+#
|
|
|
+# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
+# you may not use this file except in compliance with the License.
|
|
|
+# You may obtain a copy of the License at
|
|
|
+#
|
|
|
+# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
+#
|
|
|
+# Unless required by applicable law or agreed to in writing, software
|
|
|
+# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
+# See the License for the specific language governing permissions and
|
|
|
+# limitations under the License.
|
|
|
+
|
|
|
+import sys
|
|
|
+import os
|
|
|
+import shutil
|
|
|
+
|
|
|
+from error import GitError
|
|
|
+from error import ManifestParseError
|
|
|
+from git_command import GitCommand
|
|
|
+from git_config import GitConfig
|
|
|
+from git_config import IsId
|
|
|
+from manifest import Manifest
|
|
|
+from progress import Progress
|
|
|
+from project import RemoteSpec
|
|
|
+from project import Project
|
|
|
+from project import MetaProject
|
|
|
+from project import R_HEADS
|
|
|
+from project import HEAD
|
|
|
+from project import _lwrite
|
|
|
+
|
|
|
+import manifest_xml
|
|
|
+
|
|
|
+GITLINK = '160000'
|
|
|
+
|
|
|
+def _rmdir(dir, top):
|
|
|
+ while dir != top:
|
|
|
+ try:
|
|
|
+ os.rmdir(dir)
|
|
|
+ except OSError:
|
|
|
+ break
|
|
|
+ dir = os.path.dirname(dir)
|
|
|
+
|
|
|
+def _rmref(gitdir, ref):
|
|
|
+ os.remove(os.path.join(gitdir, ref))
|
|
|
+ log = os.path.join(gitdir, 'logs', ref)
|
|
|
+ if os.path.exists(log):
|
|
|
+ os.remove(log)
|
|
|
+ _rmdir(os.path.dirname(log), gitdir)
|
|
|
+
|
|
|
+def _has_gitmodules(d):
|
|
|
+ return os.path.exists(os.path.join(d, '.gitmodules'))
|
|
|
+
|
|
|
+class SubmoduleManifest(Manifest):
|
|
|
+ """manifest from .gitmodules file"""
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def Is(cls, repodir):
|
|
|
+ return _has_gitmodules(os.path.dirname(repodir)) \
|
|
|
+ or _has_gitmodules(os.path.join(repodir, 'manifest')) \
|
|
|
+ or _has_gitmodules(os.path.join(repodir, 'manifests'))
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def IsBare(cls, p):
|
|
|
+ try:
|
|
|
+ p.bare_git.cat_file('-e', '%s:.gitmodules' % p.GetRevisionId())
|
|
|
+ except GitError:
|
|
|
+ return False
|
|
|
+ return True
|
|
|
+
|
|
|
+ def __init__(self, repodir):
|
|
|
+ Manifest.__init__(self, repodir)
|
|
|
+
|
|
|
+ gitdir = os.path.join(repodir, 'manifest.git')
|
|
|
+ config = GitConfig.ForRepository(gitdir = gitdir)
|
|
|
+
|
|
|
+ if config.GetBoolean('repo.mirror'):
|
|
|
+ worktree = os.path.join(repodir, 'manifest')
|
|
|
+ relpath = None
|
|
|
+ else:
|
|
|
+ worktree = self.topdir
|
|
|
+ relpath = '.'
|
|
|
+
|
|
|
+ self.manifestProject = MetaProject(self, '__manifest__',
|
|
|
+ gitdir = gitdir,
|
|
|
+ worktree = worktree,
|
|
|
+ relpath = relpath)
|
|
|
+ self._modules = GitConfig(os.path.join(worktree, '.gitmodules'),
|
|
|
+ pickleFile = os.path.join(
|
|
|
+ repodir, '.repopickle_gitmodules'
|
|
|
+ ))
|
|
|
+ self._review = GitConfig(os.path.join(worktree, '.review'),
|
|
|
+ pickleFile = os.path.join(
|
|
|
+ repodir, '.repopickle_review'
|
|
|
+ ))
|
|
|
+ self._Unload()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def projects(self):
|
|
|
+ self._Load()
|
|
|
+ return self._projects
|
|
|
+
|
|
|
+ def InitBranch(self):
|
|
|
+ m = self.manifestProject
|
|
|
+ if m.CurrentBranch is None:
|
|
|
+ b = m.revisionExpr
|
|
|
+ if b.startswith(R_HEADS):
|
|
|
+ b = b[len(R_HEADS):]
|
|
|
+ return m.StartBranch(b)
|
|
|
+ return True
|
|
|
+
|
|
|
+ def SetMRefs(self, project):
|
|
|
+ if project.revisionId is None:
|
|
|
+ # Special project, e.g. the manifest or repo executable.
|
|
|
+ #
|
|
|
+ return
|
|
|
+
|
|
|
+ ref = 'refs/remotes/m'
|
|
|
+ cur = project.bare_ref.get(ref)
|
|
|
+ exp = project.revisionId
|
|
|
+ if cur != exp:
|
|
|
+ msg = 'manifest set to %s' % exp
|
|
|
+ project.bare_git.UpdateRef(ref, exp, message = msg, detach = True)
|
|
|
+
|
|
|
+ ref = 'refs/remotes/m-revision'
|
|
|
+ cur = project.bare_ref.symref(ref)
|
|
|
+ exp = project.revisionExpr
|
|
|
+ if exp is None:
|
|
|
+ if cur:
|
|
|
+ _rmref(project.gitdir, ref)
|
|
|
+ elif cur != exp:
|
|
|
+ remote = project.GetRemote(project.remote.name)
|
|
|
+ dst = remote.ToLocal(exp)
|
|
|
+ msg = 'manifest set to %s (%s)' % (exp, dst)
|
|
|
+ project.bare_git.symbolic_ref('-m', msg, ref, dst)
|
|
|
+
|
|
|
+ def Upgrade_Local(self, old):
|
|
|
+ if isinstance(old, manifest_xml.XmlManifest):
|
|
|
+ self.FromXml_Local_1(old, checkout=True)
|
|
|
+ self.FromXml_Local_2(old)
|
|
|
+ else:
|
|
|
+ raise ManifestParseError, 'cannot upgrade manifest'
|
|
|
+
|
|
|
+ def FromXml_Local_1(self, old, checkout):
|
|
|
+ os.rename(old.manifestProject.gitdir,
|
|
|
+ os.path.join(old.repodir, 'manifest.git'))
|
|
|
+
|
|
|
+ oldmp = old.manifestProject
|
|
|
+ oldBranch = oldmp.CurrentBranch
|
|
|
+ b = oldmp.GetBranch(oldBranch).merge
|
|
|
+ if not b:
|
|
|
+ raise ManifestParseError, 'cannot upgrade manifest'
|
|
|
+ if b.startswith(R_HEADS):
|
|
|
+ b = b[len(R_HEADS):]
|
|
|
+
|
|
|
+ newmp = self.manifestProject
|
|
|
+ self._CleanOldMRefs(newmp)
|
|
|
+ if oldBranch != b:
|
|
|
+ newmp.bare_git.branch('-m', oldBranch, b)
|
|
|
+ newmp.config.ClearCache()
|
|
|
+
|
|
|
+ old_remote = newmp.GetBranch(b).remote.name
|
|
|
+ act_remote = self._GuessRemoteName(old)
|
|
|
+ if old_remote != act_remote:
|
|
|
+ newmp.bare_git.remote('rename', old_remote, act_remote)
|
|
|
+ newmp.config.ClearCache()
|
|
|
+ newmp.remote.name = act_remote
|
|
|
+ print >>sys.stderr, "Assuming remote named '%s'" % act_remote
|
|
|
+
|
|
|
+ if checkout:
|
|
|
+ for p in old.projects.values():
|
|
|
+ for c in p.copyfiles:
|
|
|
+ if os.path.exists(c.abs_dest):
|
|
|
+ os.remove(c.abs_dest)
|
|
|
+ newmp._InitWorkTree()
|
|
|
+ else:
|
|
|
+ newmp._LinkWorkTree()
|
|
|
+
|
|
|
+ _lwrite(os.path.join(newmp.worktree,'.git',HEAD),
|
|
|
+ 'ref: refs/heads/%s\n' % b)
|
|
|
+
|
|
|
+ def _GuessRemoteName(self, old):
|
|
|
+ used = {}
|
|
|
+ for p in old.projects.values():
|
|
|
+ n = p.remote.name
|
|
|
+ used[n] = used.get(n, 0) + 1
|
|
|
+
|
|
|
+ remote_name = 'origin'
|
|
|
+ remote_used = 0
|
|
|
+ for n in used.keys():
|
|
|
+ if remote_used < used[n]:
|
|
|
+ remote_used = used[n]
|
|
|
+ remote_name = n
|
|
|
+ return remote_name
|
|
|
+
|
|
|
+ def FromXml_Local_2(self, old):
|
|
|
+ shutil.rmtree(old.manifestProject.worktree)
|
|
|
+ os.remove(old._manifestFile)
|
|
|
+
|
|
|
+ my_remote = self._Remote().name
|
|
|
+ new_base = os.path.join(self.repodir, 'projects')
|
|
|
+ old_base = os.path.join(self.repodir, 'projects.old')
|
|
|
+ os.rename(new_base, old_base)
|
|
|
+ os.makedirs(new_base)
|
|
|
+
|
|
|
+ info = []
|
|
|
+ pm = Progress('Converting projects', len(self.projects))
|
|
|
+ for p in self.projects.values():
|
|
|
+ pm.update()
|
|
|
+
|
|
|
+ old_p = old.projects.get(p.name)
|
|
|
+ old_gitdir = os.path.join(old_base, '%s.git' % p.relpath)
|
|
|
+ if not os.path.isdir(old_gitdir):
|
|
|
+ continue
|
|
|
+
|
|
|
+ parent = os.path.dirname(p.gitdir)
|
|
|
+ if not os.path.isdir(parent):
|
|
|
+ os.makedirs(parent)
|
|
|
+ os.rename(old_gitdir, p.gitdir)
|
|
|
+ _rmdir(os.path.dirname(old_gitdir), self.repodir)
|
|
|
+
|
|
|
+ if not os.path.isdir(p.worktree):
|
|
|
+ os.makedirs(p.worktree)
|
|
|
+
|
|
|
+ if os.path.isdir(os.path.join(p.worktree, '.git')):
|
|
|
+ p._LinkWorkTree(relink=True)
|
|
|
+
|
|
|
+ self._CleanOldMRefs(p)
|
|
|
+ if old_p and old_p.remote.name != my_remote:
|
|
|
+ info.append("%s/: renamed remote '%s' to '%s'" \
|
|
|
+ % (p.relpath, old_p.remote.name, my_remote))
|
|
|
+ p.bare_git.remote('rename', old_p.remote.name, my_remote)
|
|
|
+ p.config.ClearCache()
|
|
|
+
|
|
|
+ self.SetMRefs(p)
|
|
|
+ pm.end()
|
|
|
+ for i in info:
|
|
|
+ print >>sys.stderr, i
|
|
|
+
|
|
|
+ def _CleanOldMRefs(self, p):
|
|
|
+ all_refs = p._allrefs
|
|
|
+ for ref in all_refs.keys():
|
|
|
+ if ref.startswith(manifest_xml.R_M):
|
|
|
+ if p.bare_ref.symref(ref) != '':
|
|
|
+ _rmref(p.gitdir, ref)
|
|
|
+ else:
|
|
|
+ p.bare_git.DeleteRef(ref, all_refs[ref])
|
|
|
+
|
|
|
+ def FromXml_Definition(self, old):
|
|
|
+ """Convert another manifest representation to this one.
|
|
|
+ """
|
|
|
+ mp = self.manifestProject
|
|
|
+ gm = self._modules
|
|
|
+ gr = self._review
|
|
|
+
|
|
|
+ fd = open(os.path.join(mp.worktree, '.gitignore'), 'ab')
|
|
|
+ fd.write('/.repo\n')
|
|
|
+ fd.close()
|
|
|
+
|
|
|
+ sort_projects = list(old.projects.keys())
|
|
|
+ sort_projects.sort()
|
|
|
+
|
|
|
+ b = mp.GetBranch(mp.CurrentBranch).merge
|
|
|
+ if b.startswith(R_HEADS):
|
|
|
+ b = b[len(R_HEADS):]
|
|
|
+
|
|
|
+ info = []
|
|
|
+ pm = Progress('Converting manifest', len(sort_projects))
|
|
|
+ for p in sort_projects:
|
|
|
+ pm.update()
|
|
|
+ p = old.projects[p]
|
|
|
+
|
|
|
+ gm.SetString('submodule.%s.path' % p.name, p.relpath)
|
|
|
+ gm.SetString('submodule.%s.url' % p.name, p.remote.url)
|
|
|
+
|
|
|
+ if gr.GetString('review.url') is None:
|
|
|
+ gr.SetString('review.url', p.remote.review)
|
|
|
+ elif gr.GetString('review.url') != p.remote.review:
|
|
|
+ gr.SetString('review.%s.url' % p.name, p.remote.review)
|
|
|
+
|
|
|
+ r = p.revisionExpr
|
|
|
+ if r and not IsId(r):
|
|
|
+ if r.startswith(R_HEADS):
|
|
|
+ r = r[len(R_HEADS):]
|
|
|
+ if r == b:
|
|
|
+ r = '.'
|
|
|
+ gm.SetString('submodule.%s.revision' % p.name, r)
|
|
|
+
|
|
|
+ for c in p.copyfiles:
|
|
|
+ info.append('Moved %s out of %s' % (c.src, p.relpath))
|
|
|
+ c._Copy()
|
|
|
+ p.work_git.rm(c.src)
|
|
|
+ mp.work_git.add(c.dest)
|
|
|
+
|
|
|
+ self.SetRevisionId(p.relpath, p.GetRevisionId())
|
|
|
+ mp.work_git.add('.gitignore', '.gitmodules', '.review')
|
|
|
+ pm.end()
|
|
|
+ for i in info:
|
|
|
+ print >>sys.stderr, i
|
|
|
+
|
|
|
+ def _Unload(self):
|
|
|
+ self._loaded = False
|
|
|
+ self._projects = {}
|
|
|
+ self._revisionIds = None
|
|
|
+ self.branch = None
|
|
|
+
|
|
|
+ def _Load(self):
|
|
|
+ if not self._loaded:
|
|
|
+ f = os.path.join(self.repodir, manifest_xml.LOCAL_MANIFEST_NAME)
|
|
|
+ if os.path.exists(f):
|
|
|
+ print >>sys.stderr, 'warning: ignoring %s' % f
|
|
|
+
|
|
|
+ m = self.manifestProject
|
|
|
+ b = m.CurrentBranch
|
|
|
+ if not b:
|
|
|
+ raise ManifestParseError, 'manifest cannot be on detached HEAD'
|
|
|
+ b = m.GetBranch(b).merge
|
|
|
+ if b.startswith(R_HEADS):
|
|
|
+ b = b[len(R_HEADS):]
|
|
|
+ self.branch = b
|
|
|
+ m.remote.name = self._Remote().name
|
|
|
+
|
|
|
+ self._ParseModules()
|
|
|
+
|
|
|
+ if self.IsMirror:
|
|
|
+ self._AddMetaProjectMirror(self.repoProject)
|
|
|
+ self._AddMetaProjectMirror(self.manifestProject)
|
|
|
+
|
|
|
+ self._loaded = True
|
|
|
+
|
|
|
+ def _ParseModules(self):
|
|
|
+ byPath = dict()
|
|
|
+ for name in self._modules.GetSubSections('submodule'):
|
|
|
+ p = self._ParseProject(name)
|
|
|
+ if self._projects.get(p.name):
|
|
|
+ raise ManifestParseError, 'duplicate project "%s"' % p.name
|
|
|
+ if byPath.get(p.relpath):
|
|
|
+ raise ManifestParseError, 'duplicate path "%s"' % p.relpath
|
|
|
+ self._projects[p.name] = p
|
|
|
+ byPath[p.relpath] = p
|
|
|
+
|
|
|
+ for relpath in self._allRevisionIds.keys():
|
|
|
+ if relpath not in byPath:
|
|
|
+ raise ManifestParseError, \
|
|
|
+ 'project "%s" not in .gitmodules' \
|
|
|
+ % relpath
|
|
|
+
|
|
|
+ def _Remote(self):
|
|
|
+ m = self.manifestProject
|
|
|
+ b = m.GetBranch(m.CurrentBranch)
|
|
|
+ return b.remote
|
|
|
+
|
|
|
+ def _ResolveUrl(self, url):
|
|
|
+ if url.startswith('./') or url.startswith('../'):
|
|
|
+ base = self._Remote().url
|
|
|
+ try:
|
|
|
+ base = base[:base.rindex('/')+1]
|
|
|
+ except ValueError:
|
|
|
+ base = base[:base.rindex(':')+1]
|
|
|
+ if url.startswith('./'):
|
|
|
+ url = url[2:]
|
|
|
+ while '/' in base and url.startswith('../'):
|
|
|
+ base = base[:base.rindex('/')+1]
|
|
|
+ url = url[3:]
|
|
|
+ return base + url
|
|
|
+ return url
|
|
|
+
|
|
|
+ def _GetRevisionId(self, path):
|
|
|
+ return self._allRevisionIds.get(path)
|
|
|
+
|
|
|
+ @property
|
|
|
+ def _allRevisionIds(self):
|
|
|
+ if self._revisionIds is None:
|
|
|
+ a = dict()
|
|
|
+ p = GitCommand(self.manifestProject,
|
|
|
+ ['ls-files','-z','--stage'],
|
|
|
+ capture_stdout = True)
|
|
|
+ for line in p.process.stdout.read().split('\0')[:-1]:
|
|
|
+ l_info, l_path = line.split('\t', 2)
|
|
|
+ l_mode, l_id, l_stage = l_info.split(' ', 2)
|
|
|
+ if l_mode == GITLINK and l_stage == '0':
|
|
|
+ a[l_path] = l_id
|
|
|
+ p.Wait()
|
|
|
+ self._revisionIds = a
|
|
|
+ return self._revisionIds
|
|
|
+
|
|
|
+ def SetRevisionId(self, path, id):
|
|
|
+ self.manifestProject.work_git.update_index(
|
|
|
+ '--add','--cacheinfo', GITLINK, id, path)
|
|
|
+
|
|
|
+ def _ParseProject(self, name):
|
|
|
+ gm = self._modules
|
|
|
+ gr = self._review
|
|
|
+
|
|
|
+ path = gm.GetString('submodule.%s.path' % name)
|
|
|
+ if not path:
|
|
|
+ path = name
|
|
|
+
|
|
|
+ revId = self._GetRevisionId(path)
|
|
|
+ if not revId:
|
|
|
+ raise ManifestParseError(
|
|
|
+ 'submodule "%s" has no revision at "%s"' \
|
|
|
+ % (name, path))
|
|
|
+
|
|
|
+ url = gm.GetString('submodule.%s.url' % name)
|
|
|
+ if not url:
|
|
|
+ url = name
|
|
|
+ url = self._ResolveUrl(url)
|
|
|
+
|
|
|
+ review = gr.GetString('review.%s.url' % name)
|
|
|
+ if not review:
|
|
|
+ review = gr.GetString('review.url')
|
|
|
+ if not review:
|
|
|
+ review = self._Remote().review
|
|
|
+
|
|
|
+ remote = RemoteSpec(self._Remote().name, url, review)
|
|
|
+ revExpr = gm.GetString('submodule.%s.revision' % name)
|
|
|
+ if revExpr == '.':
|
|
|
+ revExpr = self.branch
|
|
|
+
|
|
|
+ if self.IsMirror:
|
|
|
+ relpath = None
|
|
|
+ worktree = None
|
|
|
+ gitdir = os.path.join(self.topdir, '%s.git' % name)
|
|
|
+ else:
|
|
|
+ worktree = os.path.join(self.topdir, path)
|
|
|
+ gitdir = os.path.join(self.repodir, 'projects/%s.git' % name)
|
|
|
+
|
|
|
+ return Project(manifest = self,
|
|
|
+ name = name,
|
|
|
+ remote = remote,
|
|
|
+ gitdir = gitdir,
|
|
|
+ worktree = worktree,
|
|
|
+ relpath = path,
|
|
|
+ revisionExpr = revExpr,
|
|
|
+ revisionId = revId)
|
|
|
+
|
|
|
+ def _AddMetaProjectMirror(self, m):
|
|
|
+ m_url = m.GetRemote(m.remote.name).url
|
|
|
+ if m_url.endswith('/.git'):
|
|
|
+ raise ManifestParseError, 'refusing to mirror %s' % m_url
|
|
|
+
|
|
|
+ name = self._GuessMetaName(m_url)
|
|
|
+ if name.endswith('.git'):
|
|
|
+ name = name[:-4]
|
|
|
+
|
|
|
+ if name not in self._projects:
|
|
|
+ m.PreSync()
|
|
|
+ gitdir = os.path.join(self.topdir, '%s.git' % name)
|
|
|
+ project = Project(manifest = self,
|
|
|
+ name = name,
|
|
|
+ remote = RemoteSpec(self._Remote().name, m_url),
|
|
|
+ gitdir = gitdir,
|
|
|
+ worktree = None,
|
|
|
+ relpath = None,
|
|
|
+ revisionExpr = m.revisionExpr,
|
|
|
+ revisionId = None)
|
|
|
+ self._projects[project.name] = project
|
|
|
+
|
|
|
+ def _GuessMetaName(self, m_url):
|
|
|
+ parts = m_url.split('/')
|
|
|
+ name = parts[-1]
|
|
|
+ parts = parts[0:-1]
|
|
|
+ s = len(parts) - 1
|
|
|
+ while s > 0:
|
|
|
+ l = '/'.join(parts[0:s]) + '/'
|
|
|
+ r = '/'.join(parts[s:]) + '/'
|
|
|
+ for p in self._projects.values():
|
|
|
+ if p.name.startswith(r) and p.remote.url.startswith(l):
|
|
|
+ return r + name
|
|
|
+ s -= 1
|
|
|
+ return m_url[m_url.rindex('/') + 1:]
|