You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

345 lines
11KB

  1. """Package with general repository related functions"""
  2. import os
  3. import stat
  4. from string import digits
  5. from git.compat import xrange
  6. from git.exc import WorkTreeRepositoryUnsupported
  7. from git.objects import Object
  8. from git.refs import SymbolicReference
  9. from git.util import hex_to_bin, bin_to_hex, decygpath
  10. from gitdb.exc import (
  11. BadObject,
  12. BadName,
  13. )
  14. import os.path as osp
  15. from git.cmd import Git
  16. __all__ = ('rev_parse', 'is_git_dir', 'touch', 'find_submodule_git_dir', 'name_to_object', 'short_to_long', 'deref_tag',
  17. 'to_commit', 'find_worktree_git_dir')
  18. def touch(filename):
  19. with open(filename, "ab"):
  20. pass
  21. return filename
  22. def is_git_dir(d):
  23. """ This is taken from the git setup.c:is_git_directory
  24. function.
  25. @throws WorkTreeRepositoryUnsupported if it sees a worktree directory. It's quite hacky to do that here,
  26. but at least clearly indicates that we don't support it.
  27. There is the unlikely danger to throw if we see directories which just look like a worktree dir,
  28. but are none."""
  29. if osp.isdir(d):
  30. if osp.isdir(osp.join(d, 'objects')) and osp.isdir(osp.join(d, 'refs')):
  31. headref = osp.join(d, 'HEAD')
  32. return osp.isfile(headref) or \
  33. (osp.islink(headref) and
  34. os.readlink(headref).startswith('refs'))
  35. elif (osp.isfile(osp.join(d, 'gitdir')) and
  36. osp.isfile(osp.join(d, 'commondir')) and
  37. osp.isfile(osp.join(d, 'gitfile'))):
  38. raise WorkTreeRepositoryUnsupported(d)
  39. return False
  40. def find_worktree_git_dir(dotgit):
  41. """Search for a gitdir for this worktree."""
  42. try:
  43. statbuf = os.stat(dotgit)
  44. except OSError:
  45. return None
  46. if not stat.S_ISREG(statbuf.st_mode):
  47. return None
  48. try:
  49. lines = open(dotgit, 'r').readlines()
  50. for key, value in [line.strip().split(': ') for line in lines]:
  51. if key == 'gitdir':
  52. return value
  53. except ValueError:
  54. pass
  55. return None
  56. def find_submodule_git_dir(d):
  57. """Search for a submodule repo."""
  58. if is_git_dir(d):
  59. return d
  60. try:
  61. with open(d) as fp:
  62. content = fp.read().rstrip()
  63. except (IOError, OSError):
  64. # it's probably not a file
  65. pass
  66. else:
  67. if content.startswith('gitdir: '):
  68. path = content[8:]
  69. if Git.is_cygwin():
  70. ## Cygwin creates submodules prefixed with `/cygdrive/...` suffixes.
  71. path = decygpath(path)
  72. if not osp.isabs(path):
  73. path = osp.normpath(osp.join(osp.dirname(d), path))
  74. return find_submodule_git_dir(path)
  75. # end handle exception
  76. return None
  77. def short_to_long(odb, hexsha):
  78. """:return: long hexadecimal sha1 from the given less-than-40 byte hexsha
  79. or None if no candidate could be found.
  80. :param hexsha: hexsha with less than 40 byte"""
  81. try:
  82. return bin_to_hex(odb.partial_to_complete_sha_hex(hexsha))
  83. except BadObject:
  84. return None
  85. # END exception handling
  86. def name_to_object(repo, name, return_ref=False):
  87. """
  88. :return: object specified by the given name, hexshas ( short and long )
  89. as well as references are supported
  90. :param return_ref: if name specifies a reference, we will return the reference
  91. instead of the object. Otherwise it will raise BadObject or BadName
  92. """
  93. hexsha = None
  94. # is it a hexsha ? Try the most common ones, which is 7 to 40
  95. if repo.re_hexsha_shortened.match(name):
  96. if len(name) != 40:
  97. # find long sha for short sha
  98. hexsha = short_to_long(repo.odb, name)
  99. else:
  100. hexsha = name
  101. # END handle short shas
  102. # END find sha if it matches
  103. # if we couldn't find an object for what seemed to be a short hexsha
  104. # try to find it as reference anyway, it could be named 'aaa' for instance
  105. if hexsha is None:
  106. for base in ('%s', 'refs/%s', 'refs/tags/%s', 'refs/heads/%s', 'refs/remotes/%s', 'refs/remotes/%s/HEAD'):
  107. try:
  108. hexsha = SymbolicReference.dereference_recursive(repo, base % name)
  109. if return_ref:
  110. return SymbolicReference(repo, base % name)
  111. # END handle symbolic ref
  112. break
  113. except ValueError:
  114. pass
  115. # END for each base
  116. # END handle hexsha
  117. # didn't find any ref, this is an error
  118. if return_ref:
  119. raise BadObject("Couldn't find reference named %r" % name)
  120. # END handle return ref
  121. # tried everything ? fail
  122. if hexsha is None:
  123. raise BadName(name)
  124. # END assert hexsha was found
  125. return Object.new_from_sha(repo, hex_to_bin(hexsha))
  126. def deref_tag(tag):
  127. """Recursively dereference a tag and return the resulting object"""
  128. while True:
  129. try:
  130. tag = tag.object
  131. except AttributeError:
  132. break
  133. # END dereference tag
  134. return tag
  135. def to_commit(obj):
  136. """Convert the given object to a commit if possible and return it"""
  137. if obj.type == 'tag':
  138. obj = deref_tag(obj)
  139. if obj.type != "commit":
  140. raise ValueError("Cannot convert object %r to type commit" % obj)
  141. # END verify type
  142. return obj
  143. def rev_parse(repo, rev):
  144. """
  145. :return: Object at the given revision, either Commit, Tag, Tree or Blob
  146. :param rev: git-rev-parse compatible revision specification as string, please see
  147. http://www.kernel.org/pub/software/scm/git/docs/git-rev-parse.html
  148. for details
  149. :raise BadObject: if the given revision could not be found
  150. :raise ValueError: If rev couldn't be parsed
  151. :raise IndexError: If invalid reflog index is specified"""
  152. # colon search mode ?
  153. if rev.startswith(':/'):
  154. # colon search mode
  155. raise NotImplementedError("commit by message search ( regex )")
  156. # END handle search
  157. obj = None
  158. ref = None
  159. output_type = "commit"
  160. start = 0
  161. parsed_to = 0
  162. lr = len(rev)
  163. while start < lr:
  164. if rev[start] not in "^~:@":
  165. start += 1
  166. continue
  167. # END handle start
  168. token = rev[start]
  169. if obj is None:
  170. # token is a rev name
  171. if start == 0:
  172. ref = repo.head.ref
  173. else:
  174. if token == '@':
  175. ref = name_to_object(repo, rev[:start], return_ref=True)
  176. else:
  177. obj = name_to_object(repo, rev[:start])
  178. # END handle token
  179. # END handle refname
  180. if ref is not None:
  181. obj = ref.commit
  182. # END handle ref
  183. # END initialize obj on first token
  184. start += 1
  185. # try to parse {type}
  186. if start < lr and rev[start] == '{':
  187. end = rev.find('}', start)
  188. if end == -1:
  189. raise ValueError("Missing closing brace to define type in %s" % rev)
  190. output_type = rev[start + 1:end] # exclude brace
  191. # handle type
  192. if output_type == 'commit':
  193. pass # default
  194. elif output_type == 'tree':
  195. try:
  196. obj = to_commit(obj).tree
  197. except (AttributeError, ValueError):
  198. pass # error raised later
  199. # END exception handling
  200. elif output_type in ('', 'blob'):
  201. if obj.type == 'tag':
  202. obj = deref_tag(obj)
  203. else:
  204. # cannot do anything for non-tags
  205. pass
  206. # END handle tag
  207. elif token == '@':
  208. # try single int
  209. assert ref is not None, "Requre Reference to access reflog"
  210. revlog_index = None
  211. try:
  212. # transform reversed index into the format of our revlog
  213. revlog_index = -(int(output_type) + 1)
  214. except ValueError:
  215. # TODO: Try to parse the other date options, using parse_date
  216. # maybe
  217. raise NotImplementedError("Support for additional @{...} modes not implemented")
  218. # END handle revlog index
  219. try:
  220. entry = ref.log_entry(revlog_index)
  221. except IndexError:
  222. raise IndexError("Invalid revlog index: %i" % revlog_index)
  223. # END handle index out of bound
  224. obj = Object.new_from_sha(repo, hex_to_bin(entry.newhexsha))
  225. # make it pass the following checks
  226. output_type = None
  227. else:
  228. raise ValueError("Invalid output type: %s ( in %s )" % (output_type, rev))
  229. # END handle output type
  230. # empty output types don't require any specific type, its just about dereferencing tags
  231. if output_type and obj.type != output_type:
  232. raise ValueError("Could not accommodate requested object type %r, got %s" % (output_type, obj.type))
  233. # END verify output type
  234. start = end + 1 # skip brace
  235. parsed_to = start
  236. continue
  237. # END parse type
  238. # try to parse a number
  239. num = 0
  240. if token != ":":
  241. found_digit = False
  242. while start < lr:
  243. if rev[start] in digits:
  244. num = num * 10 + int(rev[start])
  245. start += 1
  246. found_digit = True
  247. else:
  248. break
  249. # END handle number
  250. # END number parse loop
  251. # no explicit number given, 1 is the default
  252. # It could be 0 though
  253. if not found_digit:
  254. num = 1
  255. # END set default num
  256. # END number parsing only if non-blob mode
  257. parsed_to = start
  258. # handle hierarchy walk
  259. try:
  260. if token == "~":
  261. obj = to_commit(obj)
  262. for _ in xrange(num):
  263. obj = obj.parents[0]
  264. # END for each history item to walk
  265. elif token == "^":
  266. obj = to_commit(obj)
  267. # must be n'th parent
  268. if num:
  269. obj = obj.parents[num - 1]
  270. elif token == ":":
  271. if obj.type != "tree":
  272. obj = obj.tree
  273. # END get tree type
  274. obj = obj[rev[start:]]
  275. parsed_to = lr
  276. else:
  277. raise ValueError("Invalid token: %r" % token)
  278. # END end handle tag
  279. except (IndexError, AttributeError):
  280. raise BadName("Invalid revision spec '%s' - not enough parent commits to reach '%s%i'" % (rev, token, num))
  281. # END exception handling
  282. # END parse loop
  283. # still no obj ? Its probably a simple name
  284. if obj is None:
  285. obj = name_to_object(repo, rev)
  286. parsed_to = lr
  287. # END handle simple name
  288. if obj is None:
  289. raise ValueError("Revision specifier could not be parsed: %s" % rev)
  290. if parsed_to != lr:
  291. raise ValueError("Didn't consume complete rev spec %s, consumed part: %s" % (rev, rev[:parsed_to]))
  292. return obj