# base.py # Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors # # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php from .util import get_object_type_by_name from git.util import LazyMixin, join_path_native, stream_copy from gitdb.util import ( bin_to_hex, basename ) import gitdb.typ as dbtyp _assertion_msg_format = "Created object %r whose python type %r disagrees with the acutal git object type %r" __all__ = ("Object", "IndexObject") class Object(LazyMixin): """Implements an Object which may be Blobs, Trees, Commits and Tags""" NULL_HEX_SHA = '0' * 40 NULL_BIN_SHA = b'\0' * 20 TYPES = (dbtyp.str_blob_type, dbtyp.str_tree_type, dbtyp.str_commit_type, dbtyp.str_tag_type) __slots__ = ("repo", "binsha", "size") type = None # to be set by subclass def __init__(self, repo, binsha): """Initialize an object by identifying it by its binary sha. All keyword arguments will be set on demand if None. :param repo: repository this object is located in :param binsha: 20 byte SHA1""" super(Object, self).__init__() self.repo = repo self.binsha = binsha assert len(binsha) == 20, "Require 20 byte binary sha, got %r, len = %i" % (binsha, len(binsha)) @classmethod def new(cls, repo, id): """ :return: New Object instance of a type appropriate to the object type behind id. The id of the newly created object will be a binsha even though the input id may have been a Reference or Rev-Spec :param id: reference, rev-spec, or hexsha :note: This cannot be a __new__ method as it would always call __init__ with the input id which is not necessarily a binsha.""" return repo.rev_parse(str(id)) @classmethod def new_from_sha(cls, repo, sha1): """ :return: new object instance of a type appropriate to represent the given binary sha1 :param sha1: 20 byte binary sha1""" if sha1 == cls.NULL_BIN_SHA: # the NULL binsha is always the root commit return get_object_type_by_name(b'commit')(repo, sha1) # END handle special case oinfo = repo.odb.info(sha1) inst = get_object_type_by_name(oinfo.type)(repo, oinfo.binsha) inst.size = oinfo.size return inst def _set_cache_(self, attr): """Retrieve object information""" if attr == "size": oinfo = self.repo.odb.info(self.binsha) self.size = oinfo.size # assert oinfo.type == self.type, _assertion_msg_format % (self.binsha, oinfo.type, self.type) else: super(Object, self)._set_cache_(attr) def __eq__(self, other): """:return: True if the objects have the same SHA1""" if not hasattr(other, 'binsha'): return False return self.binsha == other.binsha def __ne__(self, other): """:return: True if the objects do not have the same SHA1 """ if not hasattr(other, 'binsha'): return True return self.binsha != other.binsha def __hash__(self): """:return: Hash of our id allowing objects to be used in dicts and sets""" return hash(self.binsha) def __str__(self): """:return: string of our SHA1 as understood by all git commands""" return self.hexsha def __repr__(self): """:return: string with pythonic representation of our object""" return '' % (self.__class__.__name__, self.hexsha) @property def hexsha(self): """:return: 40 byte hex version of our 20 byte binary sha""" # b2a_hex produces bytes return bin_to_hex(self.binsha).decode('ascii') @property def data_stream(self): """ :return: File Object compatible stream to the uncompressed raw data of the object :note: returned streams must be read in order""" return self.repo.odb.stream(self.binsha) def stream_data(self, ostream): """Writes our data directly to the given output stream :param ostream: File object compatible stream object. :return: self""" istream = self.repo.odb.stream(self.binsha) stream_copy(istream, ostream) return self class IndexObject(Object): """Base for all objects that can be part of the index file , namely Tree, Blob and SubModule objects""" __slots__ = ("path", "mode") # for compatability with iterable lists _id_attribute_ = 'path' def __init__(self, repo, binsha, mode=None, path=None): """Initialize a newly instanced IndexObject :param repo: is the Repo we are located in :param binsha: 20 byte sha1 :param mode: is the stat compatible file mode as int, use the stat module to evaluate the infomration :param path: is the path to the file in the file system, relative to the git repository root, i.e. file.ext or folder/other.ext :note: Path may not be set of the index object has been created directly as it cannot be retrieved without knowing the parent tree.""" super(IndexObject, self).__init__(repo, binsha) if mode is not None: self.mode = mode if path is not None: self.path = path def __hash__(self): """ :return: Hash of our path as index items are uniquely identifyable by path, not by their data !""" return hash(self.path) def _set_cache_(self, attr): if attr in IndexObject.__slots__: # they cannot be retrieved lateron ( not without searching for them ) raise AttributeError( "Attribute '%s' unset: path and mode attributes must have been set during %s object creation" % (attr, type(self).__name__)) else: super(IndexObject, self)._set_cache_(attr) # END hanlde slot attribute @property def name(self): """:return: Name portion of the path, effectively being the basename""" return basename(self.path) @property def abspath(self): """ :return: Absolute path to this index object in the file system ( as opposed to the .path field which is a path relative to the git repository ). The returned path will be native to the system and contains '\' on windows. """ return join_path_native(self.repo.working_tree_dir, self.path)