manifest_submodule.py 14 KB

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