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.

342 lines
11KB

  1. # tree.py
  2. # Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors
  3. #
  4. # This module is part of GitPython and is released under
  5. # the BSD License: http://www.opensource.org/licenses/bsd-license.php
  6. from git.util import join_path
  7. import git.diff as diff
  8. from git.util import to_bin_sha
  9. from . import util
  10. from .base import IndexObject
  11. from .blob import Blob
  12. from .submodule.base import Submodule
  13. from git.compat import string_types
  14. from .fun import (
  15. tree_entries_from_data,
  16. tree_to_stream
  17. )
  18. from git.compat import PY3
  19. if PY3:
  20. cmp = lambda a, b: (a > b) - (a < b)
  21. __all__ = ("TreeModifier", "Tree")
  22. def git_cmp(t1, t2):
  23. a, b = t1[2], t2[2]
  24. len_a, len_b = len(a), len(b)
  25. min_len = min(len_a, len_b)
  26. min_cmp = cmp(a[:min_len], b[:min_len])
  27. if min_cmp:
  28. return min_cmp
  29. return len_a - len_b
  30. def merge_sort(a, cmp):
  31. if len(a) < 2:
  32. return
  33. mid = len(a) // 2
  34. lefthalf = a[:mid]
  35. righthalf = a[mid:]
  36. merge_sort(lefthalf, cmp)
  37. merge_sort(righthalf, cmp)
  38. i = 0
  39. j = 0
  40. k = 0
  41. while i < len(lefthalf) and j < len(righthalf):
  42. if cmp(lefthalf[i], righthalf[j]) <= 0:
  43. a[k] = lefthalf[i]
  44. i = i + 1
  45. else:
  46. a[k] = righthalf[j]
  47. j = j + 1
  48. k = k + 1
  49. while i < len(lefthalf):
  50. a[k] = lefthalf[i]
  51. i = i + 1
  52. k = k + 1
  53. while j < len(righthalf):
  54. a[k] = righthalf[j]
  55. j = j + 1
  56. k = k + 1
  57. class TreeModifier(object):
  58. """A utility class providing methods to alter the underlying cache in a list-like fashion.
  59. Once all adjustments are complete, the _cache, which really is a reference to
  60. the cache of a tree, will be sorted. Assuring it will be in a serializable state"""
  61. __slots__ = '_cache'
  62. def __init__(self, cache):
  63. self._cache = cache
  64. def _index_by_name(self, name):
  65. """:return: index of an item with name, or -1 if not found"""
  66. for i, t in enumerate(self._cache):
  67. if t[2] == name:
  68. return i
  69. # END found item
  70. # END for each item in cache
  71. return -1
  72. #{ Interface
  73. def set_done(self):
  74. """Call this method once you are done modifying the tree information.
  75. It may be called several times, but be aware that each call will cause
  76. a sort operation
  77. :return self:"""
  78. merge_sort(self._cache, git_cmp)
  79. return self
  80. #} END interface
  81. #{ Mutators
  82. def add(self, sha, mode, name, force=False):
  83. """Add the given item to the tree. If an item with the given name already
  84. exists, nothing will be done, but a ValueError will be raised if the
  85. sha and mode of the existing item do not match the one you add, unless
  86. force is True
  87. :param sha: The 20 or 40 byte sha of the item to add
  88. :param mode: int representing the stat compatible mode of the item
  89. :param force: If True, an item with your name and information will overwrite
  90. any existing item with the same name, no matter which information it has
  91. :return: self"""
  92. if '/' in name:
  93. raise ValueError("Name must not contain '/' characters")
  94. if (mode >> 12) not in Tree._map_id_to_type:
  95. raise ValueError("Invalid object type according to mode %o" % mode)
  96. sha = to_bin_sha(sha)
  97. index = self._index_by_name(name)
  98. item = (sha, mode, name)
  99. if index == -1:
  100. self._cache.append(item)
  101. else:
  102. if force:
  103. self._cache[index] = item
  104. else:
  105. ex_item = self._cache[index]
  106. if ex_item[0] != sha or ex_item[1] != mode:
  107. raise ValueError("Item %r existed with different properties" % name)
  108. # END handle mismatch
  109. # END handle force
  110. # END handle name exists
  111. return self
  112. def add_unchecked(self, binsha, mode, name):
  113. """Add the given item to the tree, its correctness is assumed, which
  114. puts the caller into responsibility to assure the input is correct.
  115. For more information on the parameters, see ``add``
  116. :param binsha: 20 byte binary sha"""
  117. self._cache.append((binsha, mode, name))
  118. def __delitem__(self, name):
  119. """Deletes an item with the given name if it exists"""
  120. index = self._index_by_name(name)
  121. if index > -1:
  122. del(self._cache[index])
  123. #} END mutators
  124. class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable):
  125. """Tree objects represent an ordered list of Blobs and other Trees.
  126. ``Tree as a list``::
  127. Access a specific blob using the
  128. tree['filename'] notation.
  129. You may as well access by index
  130. blob = tree[0]
  131. """
  132. type = "tree"
  133. __slots__ = "_cache"
  134. # actual integer ids for comparison
  135. commit_id = 0o16 # equals stat.S_IFDIR | stat.S_IFLNK - a directory link
  136. blob_id = 0o10
  137. symlink_id = 0o12
  138. tree_id = 0o04
  139. _map_id_to_type = {
  140. commit_id: Submodule,
  141. blob_id: Blob,
  142. symlink_id: Blob
  143. # tree id added once Tree is defined
  144. }
  145. def __init__(self, repo, binsha, mode=tree_id << 12, path=None):
  146. super(Tree, self).__init__(repo, binsha, mode, path)
  147. @classmethod
  148. def _get_intermediate_items(cls, index_object):
  149. if index_object.type == "tree":
  150. return tuple(index_object._iter_convert_to_object(index_object._cache))
  151. return ()
  152. def _set_cache_(self, attr):
  153. if attr == "_cache":
  154. # Set the data when we need it
  155. ostream = self.repo.odb.stream(self.binsha)
  156. self._cache = tree_entries_from_data(ostream.read())
  157. else:
  158. super(Tree, self)._set_cache_(attr)
  159. # END handle attribute
  160. def _iter_convert_to_object(self, iterable):
  161. """Iterable yields tuples of (binsha, mode, name), which will be converted
  162. to the respective object representation"""
  163. for binsha, mode, name in iterable:
  164. path = join_path(self.path, name)
  165. try:
  166. yield self._map_id_to_type[mode >> 12](self.repo, binsha, mode, path)
  167. except KeyError:
  168. raise TypeError("Unknown mode %o found in tree data for path '%s'" % (mode, path))
  169. # END for each item
  170. def join(self, file):
  171. """Find the named object in this tree's contents
  172. :return: ``git.Blob`` or ``git.Tree`` or ``git.Submodule``
  173. :raise KeyError: if given file or tree does not exist in tree"""
  174. msg = "Blob or Tree named %r not found"
  175. if '/' in file:
  176. tree = self
  177. item = self
  178. tokens = file.split('/')
  179. for i, token in enumerate(tokens):
  180. item = tree[token]
  181. if item.type == 'tree':
  182. tree = item
  183. else:
  184. # safety assertion - blobs are at the end of the path
  185. if i != len(tokens) - 1:
  186. raise KeyError(msg % file)
  187. return item
  188. # END handle item type
  189. # END for each token of split path
  190. if item == self:
  191. raise KeyError(msg % file)
  192. return item
  193. else:
  194. for info in self._cache:
  195. if info[2] == file: # [2] == name
  196. return self._map_id_to_type[info[1] >> 12](self.repo, info[0], info[1],
  197. join_path(self.path, info[2]))
  198. # END for each obj
  199. raise KeyError(msg % file)
  200. # END handle long paths
  201. def __div__(self, file):
  202. """For PY2 only"""
  203. return self.join(file)
  204. def __truediv__(self, file):
  205. """For PY3 only"""
  206. return self.join(file)
  207. @property
  208. def trees(self):
  209. """:return: list(Tree, ...) list of trees directly below this tree"""
  210. return [i for i in self if i.type == "tree"]
  211. @property
  212. def blobs(self):
  213. """:return: list(Blob, ...) list of blobs directly below this tree"""
  214. return [i for i in self if i.type == "blob"]
  215. @property
  216. def cache(self):
  217. """
  218. :return: An object allowing to modify the internal cache. This can be used
  219. to change the tree's contents. When done, make sure you call ``set_done``
  220. on the tree modifier, or serialization behaviour will be incorrect.
  221. See the ``TreeModifier`` for more information on how to alter the cache"""
  222. return TreeModifier(self._cache)
  223. def traverse(self, predicate=lambda i, d: True,
  224. prune=lambda i, d: False, depth=-1, branch_first=True,
  225. visit_once=False, ignore_self=1):
  226. """For documentation, see util.Traversable.traverse
  227. Trees are set to visit_once = False to gain more performance in the traversal"""
  228. return super(Tree, self).traverse(predicate, prune, depth, branch_first, visit_once, ignore_self)
  229. # List protocol
  230. def __getslice__(self, i, j):
  231. return list(self._iter_convert_to_object(self._cache[i:j]))
  232. def __iter__(self):
  233. return self._iter_convert_to_object(self._cache)
  234. def __len__(self):
  235. return len(self._cache)
  236. def __getitem__(self, item):
  237. if isinstance(item, int):
  238. info = self._cache[item]
  239. return self._map_id_to_type[info[1] >> 12](self.repo, info[0], info[1], join_path(self.path, info[2]))
  240. if isinstance(item, string_types):
  241. # compatibility
  242. return self.join(item)
  243. # END index is basestring
  244. raise TypeError("Invalid index type: %r" % item)
  245. def __contains__(self, item):
  246. if isinstance(item, IndexObject):
  247. for info in self._cache:
  248. if item.binsha == info[0]:
  249. return True
  250. # END compare sha
  251. # END for each entry
  252. # END handle item is index object
  253. # compatibility
  254. # treat item as repo-relative path
  255. path = self.path
  256. for info in self._cache:
  257. if item == join_path(path, info[2]):
  258. return True
  259. # END for each item
  260. return False
  261. def __reversed__(self):
  262. return reversed(self._iter_convert_to_object(self._cache))
  263. def _serialize(self, stream):
  264. """Serialize this tree into the stream. Please note that we will assume
  265. our tree data to be in a sorted state. If this is not the case, serialization
  266. will not generate a correct tree representation as these are assumed to be sorted
  267. by algorithms"""
  268. tree_to_stream(self._cache, stream.write)
  269. return self
  270. def _deserialize(self, stream):
  271. self._cache = tree_entries_from_data(stream.read())
  272. return self
  273. # END tree
  274. # finalize map definition
  275. Tree._map_id_to_type[Tree.tree_id] = Tree
  276. #