import_ext.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  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. import os
  16. import random
  17. import stat
  18. import sys
  19. import urllib2
  20. import StringIO
  21. from error import GitError, ImportError
  22. from git_command import GitCommand
  23. class ImportExternal(object):
  24. """Imports a single revision from a non-git data source.
  25. Suitable for use to import a tar or zip based snapshot.
  26. """
  27. def __init__(self):
  28. self._marks = 0
  29. self._files = {}
  30. self._tempref = 'refs/repo-external/import'
  31. self._urls = []
  32. self._remap = []
  33. self.parent = None
  34. self._user_name = 'Upstream'
  35. self._user_email = 'upstream-import@none'
  36. self._user_when = 1000000
  37. self.commit = None
  38. def Clone(self):
  39. r = self.__class__()
  40. r.project = self.project
  41. for u in self._urls:
  42. r._urls.append(u)
  43. for p in self._remap:
  44. r._remap.append(_PathMap(r, p._old, p._new))
  45. return r
  46. def SetProject(self, project):
  47. self.project = project
  48. def SetVersion(self, version):
  49. self.version = version
  50. def AddUrl(self, url):
  51. self._urls.append(url)
  52. def SetParent(self, commit_hash):
  53. self.parent = commit_hash
  54. def SetCommit(self, commit_hash):
  55. self.commit = commit_hash
  56. def RemapPath(self, old, new, replace_version=True):
  57. self._remap.append(_PathMap(self, old, new))
  58. @property
  59. def TagName(self):
  60. v = ''
  61. for c in self.version:
  62. if c >= '0' and c <= '9':
  63. v += c
  64. elif c >= 'A' and c <= 'Z':
  65. v += c
  66. elif c >= 'a' and c <= 'z':
  67. v += c
  68. elif c in ('-', '_', '.', '/', '+', '@'):
  69. v += c
  70. return 'upstream/%s' % v
  71. @property
  72. def PackageName(self):
  73. n = self.project.name
  74. if n.startswith('platform/'):
  75. # This was not my finest moment...
  76. #
  77. n = n[len('platform/'):]
  78. return n
  79. def Import(self):
  80. self._need_graft = False
  81. if self.parent:
  82. try:
  83. self.project.bare_git.cat_file('-e', self.parent)
  84. except GitError:
  85. self._need_graft = True
  86. gfi = GitCommand(self.project,
  87. ['fast-import', '--force', '--quiet'],
  88. bare = True,
  89. provide_stdin = True)
  90. try:
  91. self._out = gfi.stdin
  92. try:
  93. self._UnpackFiles()
  94. self._MakeCommit()
  95. self._out.flush()
  96. finally:
  97. rc = gfi.Wait()
  98. if rc != 0:
  99. raise ImportError('fast-import failed')
  100. if self._need_graft:
  101. id = self._GraftCommit()
  102. else:
  103. id = self.project.bare_git.rev_parse('%s^0' % self._tempref)
  104. if self.commit and self.commit != id:
  105. raise ImportError('checksum mismatch: %s expected,'
  106. ' %s imported' % (self.commit, id))
  107. self._MakeTag(id)
  108. return id
  109. finally:
  110. try:
  111. self.project.bare_git.DeleteRef(self._tempref)
  112. except GitError:
  113. pass
  114. def _PickUrl(self, failed):
  115. u = map(lambda x: x.replace('%version%', self.version), self._urls)
  116. for f in failed:
  117. if f in u:
  118. u.remove(f)
  119. if len(u) == 0:
  120. return None
  121. return random.choice(u)
  122. def _OpenUrl(self):
  123. failed = {}
  124. while True:
  125. url = self._PickUrl(failed.keys())
  126. if url is None:
  127. why = 'Cannot download %s' % self.project.name
  128. if failed:
  129. why += ': one or more mirrors are down\n'
  130. bad_urls = list(failed.keys())
  131. bad_urls.sort()
  132. for url in bad_urls:
  133. why += ' %s: %s\n' % (url, failed[url])
  134. else:
  135. why += ': no mirror URLs'
  136. raise ImportError(why)
  137. print >>sys.stderr, "Getting %s ..." % url
  138. try:
  139. return urllib2.urlopen(url), url
  140. except urllib2.HTTPError, e:
  141. failed[url] = e.code
  142. except urllib2.URLError, e:
  143. failed[url] = e.reason[1]
  144. except OSError, e:
  145. failed[url] = e.strerror
  146. def _UnpackFiles(self):
  147. raise NotImplementedError
  148. def _NextMark(self):
  149. self._marks += 1
  150. return self._marks
  151. def _UnpackOneFile(self, mode, size, name, fd):
  152. if stat.S_ISDIR(mode): # directory
  153. return
  154. else:
  155. mode = self._CleanMode(mode, name)
  156. old_name = name
  157. name = self._CleanName(name)
  158. if stat.S_ISLNK(mode) and self._remap:
  159. # The link is relative to the old_name, and may need to
  160. # be rewritten according to our remap rules if it goes
  161. # up high enough in the tree structure.
  162. #
  163. dest = self._RewriteLink(fd.read(size), old_name, name)
  164. fd = StringIO.StringIO(dest)
  165. size = len(dest)
  166. fi = _File(mode, name, self._NextMark())
  167. self._out.write('blob\n')
  168. self._out.write('mark :%d\n' % fi.mark)
  169. self._out.write('data %d\n' % size)
  170. while size > 0:
  171. n = min(2048, size)
  172. self._out.write(fd.read(n))
  173. size -= n
  174. self._out.write('\n')
  175. self._files[fi.name] = fi
  176. def _SetFileMode(self, name, mode):
  177. if not stat.S_ISDIR(mode):
  178. mode = self._CleanMode(mode, name)
  179. name = self._CleanName(name)
  180. try:
  181. fi = self._files[name]
  182. except KeyError:
  183. raise ImportError('file %s was not unpacked' % name)
  184. fi.mode = mode
  185. def _RewriteLink(self, dest, relto_old, relto_new):
  186. # Drop the last components of the symlink itself
  187. # as the dest is relative to the directory its in.
  188. #
  189. relto_old = _TrimPath(relto_old)
  190. relto_new = _TrimPath(relto_new)
  191. # Resolve the link to be absolute from the top of
  192. # the archive, so we can remap its destination.
  193. #
  194. while dest.find('/./') >= 0 or dest.find('//') >= 0:
  195. dest = dest.replace('/./', '/')
  196. dest = dest.replace('//', '/')
  197. if dest.startswith('../') or dest.find('/../') > 0:
  198. dest = _FoldPath('%s/%s' % (relto_old, dest))
  199. for pm in self._remap:
  200. if pm.Matches(dest):
  201. dest = pm.Apply(dest)
  202. break
  203. dest, relto_new = _StripCommonPrefix(dest, relto_new)
  204. while relto_new:
  205. i = relto_new.find('/')
  206. if i > 0:
  207. relto_new = relto_new[i + 1:]
  208. else:
  209. relto_new = ''
  210. dest = '../' + dest
  211. return dest
  212. def _CleanMode(self, mode, name):
  213. if stat.S_ISREG(mode): # regular file
  214. if (mode & 0111) == 0:
  215. return 0644
  216. else:
  217. return 0755
  218. elif stat.S_ISLNK(mode): # symlink
  219. return stat.S_IFLNK
  220. else:
  221. raise ImportError('invalid mode %o in %s' % (mode, name))
  222. def _CleanName(self, name):
  223. old_name = name
  224. for pm in self._remap:
  225. if pm.Matches(name):
  226. name = pm.Apply(name)
  227. break
  228. while name.startswith('/'):
  229. name = name[1:]
  230. if not name:
  231. raise ImportError('path %s is empty after remap' % old_name)
  232. if name.find('/./') >= 0 or name.find('/../') >= 0:
  233. raise ImportError('path %s contains relative parts' % name)
  234. return name
  235. def _MakeCommit(self):
  236. msg = '%s %s\n' % (self.PackageName, self.version)
  237. self._out.write('commit %s\n' % self._tempref)
  238. self._out.write('committer %s <%s> %d +0000\n' % (
  239. self._user_name,
  240. self._user_email,
  241. self._user_when))
  242. self._out.write('data %d\n' % len(msg))
  243. self._out.write(msg)
  244. self._out.write('\n')
  245. if self.parent and not self._need_graft:
  246. self._out.write('from %s^0\n' % self.parent)
  247. self._out.write('deleteall\n')
  248. for f in self._files.values():
  249. self._out.write('M %o :%d %s\n' % (f.mode, f.mark, f.name))
  250. self._out.write('\n')
  251. def _GraftCommit(self):
  252. raw = self.project.bare_git.cat_file('commit', self._tempref)
  253. raw = raw.split("\n")
  254. while raw[1].startswith('parent '):
  255. del raw[1]
  256. raw.insert(1, 'parent %s' % self.parent)
  257. id = self._WriteObject('commit', "\n".join(raw))
  258. graft_file = os.path.join(self.project.gitdir, 'info/grafts')
  259. if os.path.exists(graft_file):
  260. graft_list = open(graft_file, 'rb').read().split("\n")
  261. if graft_list and graft_list[-1] == '':
  262. del graft_list[-1]
  263. else:
  264. graft_list = []
  265. exists = False
  266. for line in graft_list:
  267. if line == id:
  268. exists = True
  269. break
  270. if not exists:
  271. graft_list.append(id)
  272. graft_list.append('')
  273. fd = open(graft_file, 'wb')
  274. fd.write("\n".join(graft_list))
  275. fd.close()
  276. return id
  277. def _MakeTag(self, id):
  278. name = self.TagName
  279. raw = []
  280. raw.append('object %s' % id)
  281. raw.append('type commit')
  282. raw.append('tag %s' % name)
  283. raw.append('tagger %s <%s> %d +0000' % (
  284. self._user_name,
  285. self._user_email,
  286. self._user_when))
  287. raw.append('')
  288. raw.append('%s %s\n' % (self.PackageName, self.version))
  289. tagid = self._WriteObject('tag', "\n".join(raw))
  290. self.project.bare_git.UpdateRef('refs/tags/%s' % name, tagid)
  291. def _WriteObject(self, type, data):
  292. wo = GitCommand(self.project,
  293. ['hash-object', '-t', type, '-w', '--stdin'],
  294. bare = True,
  295. provide_stdin = True,
  296. capture_stdout = True,
  297. capture_stderr = True)
  298. wo.stdin.write(data)
  299. if wo.Wait() != 0:
  300. raise GitError('cannot create %s from (%s)' % (type, data))
  301. return wo.stdout[:-1]
  302. def _TrimPath(path):
  303. i = path.rfind('/')
  304. if i > 0:
  305. path = path[0:i]
  306. return ''
  307. def _StripCommonPrefix(a, b):
  308. while True:
  309. ai = a.find('/')
  310. bi = b.find('/')
  311. if ai > 0 and bi > 0 and a[0:ai] == b[0:bi]:
  312. a = a[ai + 1:]
  313. b = b[bi + 1:]
  314. else:
  315. break
  316. return a, b
  317. def _FoldPath(path):
  318. while True:
  319. if path.startswith('../'):
  320. return path
  321. i = path.find('/../')
  322. if i <= 0:
  323. if path.startswith('/'):
  324. return path[1:]
  325. return path
  326. lhs = path[0:i]
  327. rhs = path[i + 4:]
  328. i = lhs.rfind('/')
  329. if i > 0:
  330. path = lhs[0:i + 1] + rhs
  331. else:
  332. path = rhs
  333. class _File(object):
  334. def __init__(self, mode, name, mark):
  335. self.mode = mode
  336. self.name = name
  337. self.mark = mark
  338. class _PathMap(object):
  339. def __init__(self, imp, old, new):
  340. self._imp = imp
  341. self._old = old
  342. self._new = new
  343. def _r(self, p):
  344. return p.replace('%version%', self._imp.version)
  345. @property
  346. def old(self):
  347. return self._r(self._old)
  348. @property
  349. def new(self):
  350. return self._r(self._new)
  351. def Matches(self, name):
  352. return name.startswith(self.old)
  353. def Apply(self, name):
  354. return self.new + name[len(self.old):]