Spaces:
Running
Running
# Copyright (C) 2010, 2011 Sebastian Thiel (byronimo@gmail.com) and contributors | |
# | |
# This module is part of GitDB and is released under | |
# the New BSD License: https://opensource.org/license/bsd-3-clause/ | |
"""Contains implementations of database retrieveing objects""" | |
from gitdb.util import ( | |
join, | |
LazyMixin, | |
hex_to_bin | |
) | |
from gitdb.utils.encoding import force_text | |
from gitdb.exc import ( | |
BadObject, | |
AmbiguousObjectName | |
) | |
from itertools import chain | |
from functools import reduce | |
__all__ = ('ObjectDBR', 'ObjectDBW', 'FileDBBase', 'CompoundDB', 'CachingDB') | |
class ObjectDBR: | |
"""Defines an interface for object database lookup. | |
Objects are identified either by their 20 byte bin sha""" | |
def __contains__(self, sha): | |
return self.has_obj | |
#{ Query Interface | |
def has_object(self, sha): | |
""" | |
Whether the object identified by the given 20 bytes | |
binary sha is contained in the database | |
:return: True if the object identified by the given 20 bytes | |
binary sha is contained in the database""" | |
raise NotImplementedError("To be implemented in subclass") | |
def info(self, sha): | |
""" :return: OInfo instance | |
:param sha: bytes binary sha | |
:raise BadObject:""" | |
raise NotImplementedError("To be implemented in subclass") | |
def stream(self, sha): | |
""":return: OStream instance | |
:param sha: 20 bytes binary sha | |
:raise BadObject:""" | |
raise NotImplementedError("To be implemented in subclass") | |
def size(self): | |
""":return: amount of objects in this database""" | |
raise NotImplementedError() | |
def sha_iter(self): | |
"""Return iterator yielding 20 byte shas for all objects in this data base""" | |
raise NotImplementedError() | |
#} END query interface | |
class ObjectDBW: | |
"""Defines an interface to create objects in the database""" | |
def __init__(self, *args, **kwargs): | |
self._ostream = None | |
#{ Edit Interface | |
def set_ostream(self, stream): | |
""" | |
Adjusts the stream to which all data should be sent when storing new objects | |
:param stream: if not None, the stream to use, if None the default stream | |
will be used. | |
:return: previously installed stream, or None if there was no override | |
:raise TypeError: if the stream doesn't have the supported functionality""" | |
cstream = self._ostream | |
self._ostream = stream | |
return cstream | |
def ostream(self): | |
""" | |
Return the output stream | |
:return: overridden output stream this instance will write to, or None | |
if it will write to the default stream""" | |
return self._ostream | |
def store(self, istream): | |
""" | |
Create a new object in the database | |
:return: the input istream object with its sha set to its corresponding value | |
:param istream: IStream compatible instance. If its sha is already set | |
to a value, the object will just be stored in the our database format, | |
in which case the input stream is expected to be in object format ( header + contents ). | |
:raise IOError: if data could not be written""" | |
raise NotImplementedError("To be implemented in subclass") | |
#} END edit interface | |
class FileDBBase: | |
"""Provides basic facilities to retrieve files of interest, including | |
caching facilities to help mapping hexsha's to objects""" | |
def __init__(self, root_path): | |
"""Initialize this instance to look for its files at the given root path | |
All subsequent operations will be relative to this path | |
:raise InvalidDBRoot: | |
**Note:** The base will not perform any accessablity checking as the base | |
might not yet be accessible, but become accessible before the first | |
access.""" | |
super().__init__() | |
self._root_path = root_path | |
#{ Interface | |
def root_path(self): | |
""":return: path at which this db operates""" | |
return self._root_path | |
def db_path(self, rela_path): | |
""" | |
:return: the given relative path relative to our database root, allowing | |
to pontentially access datafiles""" | |
return join(self._root_path, force_text(rela_path)) | |
#} END interface | |
class CachingDB: | |
"""A database which uses caches to speed-up access""" | |
#{ Interface | |
def update_cache(self, force=False): | |
""" | |
Call this method if the underlying data changed to trigger an update | |
of the internal caching structures. | |
:param force: if True, the update must be performed. Otherwise the implementation | |
may decide not to perform an update if it thinks nothing has changed. | |
:return: True if an update was performed as something change indeed""" | |
# END interface | |
def _databases_recursive(database, output): | |
"""Fill output list with database from db, in order. Deals with Loose, Packed | |
and compound databases.""" | |
if isinstance(database, CompoundDB): | |
dbs = database.databases() | |
output.extend(db for db in dbs if not isinstance(db, CompoundDB)) | |
for cdb in (db for db in dbs if isinstance(db, CompoundDB)): | |
_databases_recursive(cdb, output) | |
else: | |
output.append(database) | |
# END handle database type | |
class CompoundDB(ObjectDBR, LazyMixin, CachingDB): | |
"""A database which delegates calls to sub-databases. | |
Databases are stored in the lazy-loaded _dbs attribute. | |
Define _set_cache_ to update it with your databases""" | |
def _set_cache_(self, attr): | |
if attr == '_dbs': | |
self._dbs = list() | |
elif attr == '_db_cache': | |
self._db_cache = dict() | |
else: | |
super()._set_cache_(attr) | |
def _db_query(self, sha): | |
""":return: database containing the given 20 byte sha | |
:raise BadObject:""" | |
# most databases use binary representations, prevent converting | |
# it every time a database is being queried | |
try: | |
return self._db_cache[sha] | |
except KeyError: | |
pass | |
# END first level cache | |
for db in self._dbs: | |
if db.has_object(sha): | |
self._db_cache[sha] = db | |
return db | |
# END for each database | |
raise BadObject(sha) | |
#{ ObjectDBR interface | |
def has_object(self, sha): | |
try: | |
self._db_query(sha) | |
return True | |
except BadObject: | |
return False | |
# END handle exceptions | |
def info(self, sha): | |
return self._db_query(sha).info(sha) | |
def stream(self, sha): | |
return self._db_query(sha).stream(sha) | |
def size(self): | |
""":return: total size of all contained databases""" | |
return reduce(lambda x, y: x + y, (db.size() for db in self._dbs), 0) | |
def sha_iter(self): | |
return chain(*(db.sha_iter() for db in self._dbs)) | |
#} END object DBR Interface | |
#{ Interface | |
def databases(self): | |
""":return: tuple of database instances we use for lookups""" | |
return tuple(self._dbs) | |
def update_cache(self, force=False): | |
# something might have changed, clear everything | |
self._db_cache.clear() | |
stat = False | |
for db in self._dbs: | |
if isinstance(db, CachingDB): | |
stat |= db.update_cache(force) | |
# END if is caching db | |
# END for each database to update | |
return stat | |
def partial_to_complete_sha_hex(self, partial_hexsha): | |
""" | |
:return: 20 byte binary sha1 from the given less-than-40 byte hexsha (bytes or str) | |
:param partial_hexsha: hexsha with less than 40 byte | |
:raise AmbiguousObjectName: """ | |
databases = list() | |
_databases_recursive(self, databases) | |
partial_hexsha = force_text(partial_hexsha) | |
len_partial_hexsha = len(partial_hexsha) | |
if len_partial_hexsha % 2 != 0: | |
partial_binsha = hex_to_bin(partial_hexsha + "0") | |
else: | |
partial_binsha = hex_to_bin(partial_hexsha) | |
# END assure successful binary conversion | |
candidate = None | |
for db in databases: | |
full_bin_sha = None | |
try: | |
if hasattr(db, 'partial_to_complete_sha_hex'): | |
full_bin_sha = db.partial_to_complete_sha_hex(partial_hexsha) | |
else: | |
full_bin_sha = db.partial_to_complete_sha(partial_binsha, len_partial_hexsha) | |
# END handle database type | |
except BadObject: | |
continue | |
# END ignore bad objects | |
if full_bin_sha: | |
if candidate and candidate != full_bin_sha: | |
raise AmbiguousObjectName(partial_hexsha) | |
candidate = full_bin_sha | |
# END handle candidate | |
# END for each db | |
if not candidate: | |
raise BadObject(partial_binsha) | |
return candidate | |
#} END interface | |