PageRenderTime 30ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 0ms

/hgext/convert/git.py

https://bitbucket.org/mirror/mercurial/
Python | 342 lines | 312 code | 20 blank | 10 comment | 28 complexity | 5d814328155fe00e7a609293b1f6c49e MD5 | raw file
Possible License(s): GPL-2.0
  1. # git.py - git support for the convert extension
  2. #
  3. # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
  4. #
  5. # This software may be used and distributed according to the terms of the
  6. # GNU General Public License version 2 or any later version.
  7. import os
  8. import subprocess
  9. from mercurial import util, config
  10. from mercurial.node import hex, nullid
  11. from mercurial.i18n import _
  12. from common import NoRepo, commit, converter_source, checktool
  13. class submodule(object):
  14. def __init__(self, path, node, url):
  15. self.path = path
  16. self.node = node
  17. self.url = url
  18. def hgsub(self):
  19. return "%s = [git]%s" % (self.path, self.url)
  20. def hgsubstate(self):
  21. return "%s %s" % (self.node, self.path)
  22. class convert_git(converter_source):
  23. # Windows does not support GIT_DIR= construct while other systems
  24. # cannot remove environment variable. Just assume none have
  25. # both issues.
  26. if util.safehasattr(os, 'unsetenv'):
  27. def gitopen(self, s, err=None):
  28. prevgitdir = os.environ.get('GIT_DIR')
  29. os.environ['GIT_DIR'] = self.path
  30. try:
  31. if err == subprocess.PIPE:
  32. (stdin, stdout, stderr) = util.popen3(s)
  33. return stdout
  34. elif err == subprocess.STDOUT:
  35. return self.popen_with_stderr(s)
  36. else:
  37. return util.popen(s, 'rb')
  38. finally:
  39. if prevgitdir is None:
  40. del os.environ['GIT_DIR']
  41. else:
  42. os.environ['GIT_DIR'] = prevgitdir
  43. def gitpipe(self, s):
  44. prevgitdir = os.environ.get('GIT_DIR')
  45. os.environ['GIT_DIR'] = self.path
  46. try:
  47. return util.popen3(s)
  48. finally:
  49. if prevgitdir is None:
  50. del os.environ['GIT_DIR']
  51. else:
  52. os.environ['GIT_DIR'] = prevgitdir
  53. else:
  54. def gitopen(self, s, err=None):
  55. if err == subprocess.PIPE:
  56. (sin, so, se) = util.popen3('GIT_DIR=%s %s' % (self.path, s))
  57. return so
  58. elif err == subprocess.STDOUT:
  59. return self.popen_with_stderr(s)
  60. else:
  61. return util.popen('GIT_DIR=%s %s' % (self.path, s), 'rb')
  62. def gitpipe(self, s):
  63. return util.popen3('GIT_DIR=%s %s' % (self.path, s))
  64. def popen_with_stderr(self, s):
  65. p = subprocess.Popen(s, shell=True, bufsize=-1,
  66. close_fds=util.closefds,
  67. stdin=subprocess.PIPE,
  68. stdout=subprocess.PIPE,
  69. stderr=subprocess.STDOUT,
  70. universal_newlines=False,
  71. env=None)
  72. return p.stdout
  73. def gitread(self, s):
  74. fh = self.gitopen(s)
  75. data = fh.read()
  76. return data, fh.close()
  77. def __init__(self, ui, path, rev=None):
  78. super(convert_git, self).__init__(ui, path, rev=rev)
  79. if os.path.isdir(path + "/.git"):
  80. path += "/.git"
  81. if not os.path.exists(path + "/objects"):
  82. raise NoRepo(_("%s does not look like a Git repository") % path)
  83. checktool('git', 'git')
  84. self.path = path
  85. self.submodules = []
  86. self.catfilepipe = self.gitpipe('git cat-file --batch')
  87. def after(self):
  88. for f in self.catfilepipe:
  89. f.close()
  90. def getheads(self):
  91. if not self.rev:
  92. heads, ret = self.gitread('git rev-parse --branches --remotes')
  93. heads = heads.splitlines()
  94. else:
  95. heads, ret = self.gitread("git rev-parse --verify %s" % self.rev)
  96. heads = [heads[:-1]]
  97. if ret:
  98. raise util.Abort(_('cannot retrieve git heads'))
  99. return heads
  100. def catfile(self, rev, type):
  101. if rev == hex(nullid):
  102. raise IOError
  103. self.catfilepipe[0].write(rev+'\n')
  104. self.catfilepipe[0].flush()
  105. info = self.catfilepipe[1].readline().split()
  106. if info[1] != type:
  107. raise util.Abort(_('cannot read %r object at %s') % (type, rev))
  108. size = int(info[2])
  109. data = self.catfilepipe[1].read(size)
  110. if len(data) < size:
  111. raise util.Abort(_('cannot read %r object at %s: %s') % (type, rev))
  112. # read the trailing newline
  113. self.catfilepipe[1].read(1)
  114. return data
  115. def getfile(self, name, rev):
  116. if rev == hex(nullid):
  117. raise IOError
  118. if name == '.hgsub':
  119. data = '\n'.join([m.hgsub() for m in self.submoditer()])
  120. mode = ''
  121. elif name == '.hgsubstate':
  122. data = '\n'.join([m.hgsubstate() for m in self.submoditer()])
  123. mode = ''
  124. else:
  125. data = self.catfile(rev, "blob")
  126. mode = self.modecache[(name, rev)]
  127. return data, mode
  128. def submoditer(self):
  129. null = hex(nullid)
  130. for m in sorted(self.submodules, key=lambda p: p.path):
  131. if m.node != null:
  132. yield m
  133. def parsegitmodules(self, content):
  134. """Parse the formatted .gitmodules file, example file format:
  135. [submodule "sub"]\n
  136. \tpath = sub\n
  137. \turl = git://giturl\n
  138. """
  139. self.submodules = []
  140. c = config.config()
  141. # Each item in .gitmodules starts with \t that cant be parsed
  142. c.parse('.gitmodules', content.replace('\t',''))
  143. for sec in c.sections():
  144. s = c[sec]
  145. if 'url' in s and 'path' in s:
  146. self.submodules.append(submodule(s['path'], '', s['url']))
  147. def retrievegitmodules(self, version):
  148. modules, ret = self.gitread("git show %s:%s" % (version, '.gitmodules'))
  149. if ret:
  150. raise util.Abort(_('cannot read submodules config file in %s') %
  151. version)
  152. self.parsegitmodules(modules)
  153. for m in self.submodules:
  154. node, ret = self.gitread("git rev-parse %s:%s" % (version, m.path))
  155. if ret:
  156. continue
  157. m.node = node.strip()
  158. def getchanges(self, version):
  159. self.modecache = {}
  160. fh = self.gitopen("git diff-tree -z --root -m -r %s" % version)
  161. changes = []
  162. seen = set()
  163. entry = None
  164. subexists = False
  165. subdeleted = False
  166. for l in fh.read().split('\x00'):
  167. if not entry:
  168. if not l.startswith(':'):
  169. continue
  170. entry = l
  171. continue
  172. f = l
  173. if f not in seen:
  174. seen.add(f)
  175. entry = entry.split()
  176. h = entry[3]
  177. p = (entry[1] == "100755")
  178. s = (entry[1] == "120000")
  179. if f == '.gitmodules':
  180. subexists = True
  181. if entry[4] == 'D':
  182. subdeleted = True
  183. changes.append(('.hgsub', hex(nullid)))
  184. else:
  185. changes.append(('.hgsub', ''))
  186. elif entry[1] == '160000' or entry[0] == ':160000':
  187. subexists = True
  188. else:
  189. self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
  190. changes.append((f, h))
  191. entry = None
  192. if fh.close():
  193. raise util.Abort(_('cannot read changes in %s') % version)
  194. if subexists:
  195. if subdeleted:
  196. changes.append(('.hgsubstate', hex(nullid)))
  197. else:
  198. self.retrievegitmodules(version)
  199. changes.append(('.hgsubstate', ''))
  200. return (changes, {})
  201. def getcommit(self, version):
  202. c = self.catfile(version, "commit") # read the commit hash
  203. end = c.find("\n\n")
  204. message = c[end + 2:]
  205. message = self.recode(message)
  206. l = c[:end].splitlines()
  207. parents = []
  208. author = committer = None
  209. for e in l[1:]:
  210. n, v = e.split(" ", 1)
  211. if n == "author":
  212. p = v.split()
  213. tm, tz = p[-2:]
  214. author = " ".join(p[:-2])
  215. if author[0] == "<": author = author[1:-1]
  216. author = self.recode(author)
  217. if n == "committer":
  218. p = v.split()
  219. tm, tz = p[-2:]
  220. committer = " ".join(p[:-2])
  221. if committer[0] == "<": committer = committer[1:-1]
  222. committer = self.recode(committer)
  223. if n == "parent":
  224. parents.append(v)
  225. if committer and committer != author:
  226. message += "\ncommitter: %s\n" % committer
  227. tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
  228. tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
  229. date = tm + " " + str(tz)
  230. c = commit(parents=parents, date=date, author=author, desc=message,
  231. rev=version)
  232. return c
  233. def gettags(self):
  234. tags = {}
  235. alltags = {}
  236. fh = self.gitopen('git ls-remote --tags "%s"' % self.path,
  237. err=subprocess.STDOUT)
  238. prefix = 'refs/tags/'
  239. # Build complete list of tags, both annotated and bare ones
  240. for line in fh:
  241. line = line.strip()
  242. if line.startswith("error:") or line.startswith("fatal:"):
  243. raise util.Abort(_('cannot read tags from %s') % self.path)
  244. node, tag = line.split(None, 1)
  245. if not tag.startswith(prefix):
  246. continue
  247. alltags[tag[len(prefix):]] = node
  248. if fh.close():
  249. raise util.Abort(_('cannot read tags from %s') % self.path)
  250. # Filter out tag objects for annotated tag refs
  251. for tag in alltags:
  252. if tag.endswith('^{}'):
  253. tags[tag[:-3]] = alltags[tag]
  254. else:
  255. if tag + '^{}' in alltags:
  256. continue
  257. else:
  258. tags[tag] = alltags[tag]
  259. return tags
  260. def getchangedfiles(self, version, i):
  261. changes = []
  262. if i is None:
  263. fh = self.gitopen("git diff-tree --root -m -r %s" % version)
  264. for l in fh:
  265. if "\t" not in l:
  266. continue
  267. m, f = l[:-1].split("\t")
  268. changes.append(f)
  269. else:
  270. fh = self.gitopen('git diff-tree --name-only --root -r %s '
  271. '"%s^%s" --' % (version, version, i + 1))
  272. changes = [f.rstrip('\n') for f in fh]
  273. if fh.close():
  274. raise util.Abort(_('cannot read changes in %s') % version)
  275. return changes
  276. def getbookmarks(self):
  277. bookmarks = {}
  278. # Interesting references in git are prefixed
  279. prefix = 'refs/heads/'
  280. prefixlen = len(prefix)
  281. # factor two commands
  282. gitcmd = { 'remote/': 'git ls-remote --heads origin',
  283. '': 'git show-ref'}
  284. # Origin heads
  285. for reftype in gitcmd:
  286. try:
  287. fh = self.gitopen(gitcmd[reftype], err=subprocess.PIPE)
  288. for line in fh:
  289. line = line.strip()
  290. rev, name = line.split(None, 1)
  291. if not name.startswith(prefix):
  292. continue
  293. name = '%s%s' % (reftype, name[prefixlen:])
  294. bookmarks[name] = rev
  295. except Exception:
  296. pass
  297. return bookmarks
  298. def checkrevformat(self, revstr, mapname='splicemap'):
  299. """ git revision string is a 40 byte hex """
  300. self.checkhexformat(revstr, mapname)