manifest_submodule.py 14 KB

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