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.

274 lines
8.8KB

  1. # Copyright (C) 2010, 2011 Sebastian Thiel (byronimo@gmail.com) and contributors
  2. #
  3. # This module is part of GitDB and is released under
  4. # the New BSD License: http://www.opensource.org/licenses/bsd-license.php
  5. """Contains implementations of database retrieveing objects"""
  6. from gitdb.util import (
  7. join,
  8. LazyMixin,
  9. hex_to_bin
  10. )
  11. from gitdb.utils.encoding import force_text
  12. from gitdb.exc import (
  13. BadObject,
  14. AmbiguousObjectName
  15. )
  16. from itertools import chain
  17. from functools import reduce
  18. __all__ = ('ObjectDBR', 'ObjectDBW', 'FileDBBase', 'CompoundDB', 'CachingDB')
  19. class ObjectDBR(object):
  20. """Defines an interface for object database lookup.
  21. Objects are identified either by their 20 byte bin sha"""
  22. def __contains__(self, sha):
  23. return self.has_obj
  24. #{ Query Interface
  25. def has_object(self, sha):
  26. """
  27. :return: True if the object identified by the given 20 bytes
  28. binary sha is contained in the database"""
  29. raise NotImplementedError("To be implemented in subclass")
  30. def info(self, sha):
  31. """ :return: OInfo instance
  32. :param sha: bytes binary sha
  33. :raise BadObject:"""
  34. raise NotImplementedError("To be implemented in subclass")
  35. def stream(self, sha):
  36. """:return: OStream instance
  37. :param sha: 20 bytes binary sha
  38. :raise BadObject:"""
  39. raise NotImplementedError("To be implemented in subclass")
  40. def size(self):
  41. """:return: amount of objects in this database"""
  42. raise NotImplementedError()
  43. def sha_iter(self):
  44. """Return iterator yielding 20 byte shas for all objects in this data base"""
  45. raise NotImplementedError()
  46. #} END query interface
  47. class ObjectDBW(object):
  48. """Defines an interface to create objects in the database"""
  49. def __init__(self, *args, **kwargs):
  50. self._ostream = None
  51. #{ Edit Interface
  52. def set_ostream(self, stream):
  53. """
  54. Adjusts the stream to which all data should be sent when storing new objects
  55. :param stream: if not None, the stream to use, if None the default stream
  56. will be used.
  57. :return: previously installed stream, or None if there was no override
  58. :raise TypeError: if the stream doesn't have the supported functionality"""
  59. cstream = self._ostream
  60. self._ostream = stream
  61. return cstream
  62. def ostream(self):
  63. """
  64. :return: overridden output stream this instance will write to, or None
  65. if it will write to the default stream"""
  66. return self._ostream
  67. def store(self, istream):
  68. """
  69. Create a new object in the database
  70. :return: the input istream object with its sha set to its corresponding value
  71. :param istream: IStream compatible instance. If its sha is already set
  72. to a value, the object will just be stored in the our database format,
  73. in which case the input stream is expected to be in object format ( header + contents ).
  74. :raise IOError: if data could not be written"""
  75. raise NotImplementedError("To be implemented in subclass")
  76. #} END edit interface
  77. class FileDBBase(object):
  78. """Provides basic facilities to retrieve files of interest, including
  79. caching facilities to help mapping hexsha's to objects"""
  80. def __init__(self, root_path):
  81. """Initialize this instance to look for its files at the given root path
  82. All subsequent operations will be relative to this path
  83. :raise InvalidDBRoot:
  84. **Note:** The base will not perform any accessablity checking as the base
  85. might not yet be accessible, but become accessible before the first
  86. access."""
  87. super(FileDBBase, self).__init__()
  88. self._root_path = root_path
  89. #{ Interface
  90. def root_path(self):
  91. """:return: path at which this db operates"""
  92. return self._root_path
  93. def db_path(self, rela_path):
  94. """
  95. :return: the given relative path relative to our database root, allowing
  96. to pontentially access datafiles"""
  97. return join(self._root_path, force_text(rela_path))
  98. #} END interface
  99. class CachingDB(object):
  100. """A database which uses caches to speed-up access"""
  101. #{ Interface
  102. def update_cache(self, force=False):
  103. """
  104. Call this method if the underlying data changed to trigger an update
  105. of the internal caching structures.
  106. :param force: if True, the update must be performed. Otherwise the implementation
  107. may decide not to perform an update if it thinks nothing has changed.
  108. :return: True if an update was performed as something change indeed"""
  109. # END interface
  110. def _databases_recursive(database, output):
  111. """Fill output list with database from db, in order. Deals with Loose, Packed
  112. and compound databases."""
  113. if isinstance(database, CompoundDB):
  114. dbs = database.databases()
  115. output.extend(db for db in dbs if not isinstance(db, CompoundDB))
  116. for cdb in (db for db in dbs if isinstance(db, CompoundDB)):
  117. _databases_recursive(cdb, output)
  118. else:
  119. output.append(database)
  120. # END handle database type
  121. class CompoundDB(ObjectDBR, LazyMixin, CachingDB):
  122. """A database which delegates calls to sub-databases.
  123. Databases are stored in the lazy-loaded _dbs attribute.
  124. Define _set_cache_ to update it with your databases"""
  125. def _set_cache_(self, attr):
  126. if attr == '_dbs':
  127. self._dbs = list()
  128. elif attr == '_db_cache':
  129. self._db_cache = dict()
  130. else:
  131. super(CompoundDB, self)._set_cache_(attr)
  132. def _db_query(self, sha):
  133. """:return: database containing the given 20 byte sha
  134. :raise BadObject:"""
  135. # most databases use binary representations, prevent converting
  136. # it every time a database is being queried
  137. try:
  138. return self._db_cache[sha]
  139. except KeyError:
  140. pass
  141. # END first level cache
  142. for db in self._dbs:
  143. if db.has_object(sha):
  144. self._db_cache[sha] = db
  145. return db
  146. # END for each database
  147. raise BadObject(sha)
  148. #{ ObjectDBR interface
  149. def has_object(self, sha):
  150. try:
  151. self._db_query(sha)
  152. return True
  153. except BadObject:
  154. return False
  155. # END handle exceptions
  156. def info(self, sha):
  157. return self._db_query(sha).info(sha)
  158. def stream(self, sha):
  159. return self._db_query(sha).stream(sha)
  160. def size(self):
  161. """:return: total size of all contained databases"""
  162. return reduce(lambda x, y: x + y, (db.size() for db in self._dbs), 0)
  163. def sha_iter(self):
  164. return chain(*(db.sha_iter() for db in self._dbs))
  165. #} END object DBR Interface
  166. #{ Interface
  167. def databases(self):
  168. """:return: tuple of database instances we use for lookups"""
  169. return tuple(self._dbs)
  170. def update_cache(self, force=False):
  171. # something might have changed, clear everything
  172. self._db_cache.clear()
  173. stat = False
  174. for db in self._dbs:
  175. if isinstance(db, CachingDB):
  176. stat |= db.update_cache(force)
  177. # END if is caching db
  178. # END for each database to update
  179. return stat
  180. def partial_to_complete_sha_hex(self, partial_hexsha):
  181. """
  182. :return: 20 byte binary sha1 from the given less-than-40 byte hexsha (bytes or str)
  183. :param partial_hexsha: hexsha with less than 40 byte
  184. :raise AmbiguousObjectName: """
  185. databases = list()
  186. _databases_recursive(self, databases)
  187. partial_hexsha = force_text(partial_hexsha)
  188. len_partial_hexsha = len(partial_hexsha)
  189. if len_partial_hexsha % 2 != 0:
  190. partial_binsha = hex_to_bin(partial_hexsha + "0")
  191. else:
  192. partial_binsha = hex_to_bin(partial_hexsha)
  193. # END assure successful binary conversion
  194. candidate = None
  195. for db in databases:
  196. full_bin_sha = None
  197. try:
  198. if hasattr(db, 'partial_to_complete_sha_hex'):
  199. full_bin_sha = db.partial_to_complete_sha_hex(partial_hexsha)
  200. else:
  201. full_bin_sha = db.partial_to_complete_sha(partial_binsha, len_partial_hexsha)
  202. # END handle database type
  203. except BadObject:
  204. continue
  205. # END ignore bad objects
  206. if full_bin_sha:
  207. if candidate and candidate != full_bin_sha:
  208. raise AmbiguousObjectName(partial_hexsha)
  209. candidate = full_bin_sha
  210. # END handle candidate
  211. # END for each db
  212. if not candidate:
  213. raise BadObject(partial_binsha)
  214. return candidate
  215. #} END interface