base.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. # Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors
  2. #
  3. # This module is part of GitPython and is released under the
  4. # 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
  5. __all__ = ["Object", "IndexObject"]
  6. import os.path as osp
  7. import gitdb.typ as dbtyp
  8. from git.exc import WorkTreeRepositoryUnsupported
  9. from git.util import LazyMixin, bin_to_hex, join_path_native, stream_copy
  10. from .util import get_object_type_by_name
  11. # typing ------------------------------------------------------------------
  12. from typing import Any, TYPE_CHECKING, Union
  13. from git.types import AnyGitObject, GitObjectTypeString, PathLike
  14. if TYPE_CHECKING:
  15. from gitdb.base import OStream
  16. from git.refs.reference import Reference
  17. from git.repo import Repo
  18. from .blob import Blob
  19. from .submodule.base import Submodule
  20. from .tree import Tree
  21. IndexObjUnion = Union["Tree", "Blob", "Submodule"]
  22. # --------------------------------------------------------------------------
  23. class Object(LazyMixin):
  24. """Base class for classes representing git object types.
  25. The following four leaf classes represent specific kinds of git objects:
  26. * :class:`Blob <git.objects.blob.Blob>`
  27. * :class:`Tree <git.objects.tree.Tree>`
  28. * :class:`Commit <git.objects.commit.Commit>`
  29. * :class:`TagObject <git.objects.tag.TagObject>`
  30. See :manpage:`gitglossary(7)` on:
  31. * "object": https://git-scm.com/docs/gitglossary#def_object
  32. * "object type": https://git-scm.com/docs/gitglossary#def_object_type
  33. * "blob": https://git-scm.com/docs/gitglossary#def_blob_object
  34. * "tree object": https://git-scm.com/docs/gitglossary#def_tree_object
  35. * "commit object": https://git-scm.com/docs/gitglossary#def_commit_object
  36. * "tag object": https://git-scm.com/docs/gitglossary#def_tag_object
  37. :note:
  38. See the :class:`~git.types.AnyGitObject` union type of the four leaf subclasses
  39. that represent actual git object types.
  40. :note:
  41. :class:`~git.objects.submodule.base.Submodule` is defined under the hierarchy
  42. rooted at this :class:`Object` class, even though submodules are not really a
  43. type of git object. (This also applies to its
  44. :class:`~git.objects.submodule.root.RootModule` subclass.)
  45. :note:
  46. This :class:`Object` class should not be confused with :class:`object` (the root
  47. of the class hierarchy in Python).
  48. """
  49. NULL_HEX_SHA = "0" * 40
  50. NULL_BIN_SHA = b"\0" * 20
  51. TYPES = (
  52. dbtyp.str_blob_type,
  53. dbtyp.str_tree_type,
  54. dbtyp.str_commit_type,
  55. dbtyp.str_tag_type,
  56. )
  57. __slots__ = ("repo", "binsha", "size")
  58. type: Union[GitObjectTypeString, None] = None
  59. """String identifying (a concrete :class:`Object` subtype for) a git object type.
  60. The subtypes that this may name correspond to the kinds of git objects that exist,
  61. i.e., the objects that may be present in a git repository.
  62. :note:
  63. Most subclasses represent specific types of git objects and override this class
  64. attribute accordingly. This attribute is ``None`` in the :class:`Object` base
  65. class, as well as the :class:`IndexObject` intermediate subclass, but never
  66. ``None`` in concrete leaf subclasses representing specific git object types.
  67. :note:
  68. See also :class:`~git.types.GitObjectTypeString`.
  69. """
  70. def __init__(self, repo: "Repo", binsha: bytes) -> None:
  71. """Initialize an object by identifying it by its binary sha.
  72. All keyword arguments will be set on demand if ``None``.
  73. :param repo:
  74. Repository this object is located in.
  75. :param binsha:
  76. 20 byte SHA1
  77. """
  78. super().__init__()
  79. self.repo = repo
  80. self.binsha = binsha
  81. assert len(binsha) == 20, "Require 20 byte binary sha, got %r, len = %i" % (
  82. binsha,
  83. len(binsha),
  84. )
  85. @classmethod
  86. def new(cls, repo: "Repo", id: Union[str, "Reference"]) -> AnyGitObject:
  87. """
  88. :return:
  89. New :class:`Object` instance of a type appropriate to the object type behind
  90. `id`. The id of the newly created object will be a binsha even though the
  91. input id may have been a :class:`~git.refs.reference.Reference` or rev-spec.
  92. :param id:
  93. :class:`~git.refs.reference.Reference`, rev-spec, or hexsha.
  94. :note:
  95. This cannot be a ``__new__`` method as it would always call :meth:`__init__`
  96. with the input id which is not necessarily a binsha.
  97. """
  98. return repo.rev_parse(str(id))
  99. @classmethod
  100. def new_from_sha(cls, repo: "Repo", sha1: bytes) -> AnyGitObject:
  101. """
  102. :return:
  103. New object instance of a type appropriate to represent the given binary sha1
  104. :param sha1:
  105. 20 byte binary sha1.
  106. """
  107. if sha1 == cls.NULL_BIN_SHA:
  108. # The NULL binsha is always the root commit.
  109. return get_object_type_by_name(b"commit")(repo, sha1)
  110. # END handle special case
  111. oinfo = repo.odb.info(sha1)
  112. inst = get_object_type_by_name(oinfo.type)(repo, oinfo.binsha)
  113. inst.size = oinfo.size
  114. return inst
  115. def _set_cache_(self, attr: str) -> None:
  116. """Retrieve object information."""
  117. if attr == "size":
  118. oinfo = self.repo.odb.info(self.binsha)
  119. self.size = oinfo.size # type: int
  120. else:
  121. super()._set_cache_(attr)
  122. def __eq__(self, other: Any) -> bool:
  123. """:return: ``True`` if the objects have the same SHA1"""
  124. if not hasattr(other, "binsha"):
  125. return False
  126. return self.binsha == other.binsha
  127. def __ne__(self, other: Any) -> bool:
  128. """:return: ``True`` if the objects do not have the same SHA1"""
  129. if not hasattr(other, "binsha"):
  130. return True
  131. return self.binsha != other.binsha
  132. def __hash__(self) -> int:
  133. """:return: Hash of our id allowing objects to be used in dicts and sets"""
  134. return hash(self.binsha)
  135. def __str__(self) -> str:
  136. """:return: String of our SHA1 as understood by all git commands"""
  137. return self.hexsha
  138. def __repr__(self) -> str:
  139. """:return: String with pythonic representation of our object"""
  140. return '<git.%s "%s">' % (self.__class__.__name__, self.hexsha)
  141. @property
  142. def hexsha(self) -> str:
  143. """:return: 40 byte hex version of our 20 byte binary sha"""
  144. # b2a_hex produces bytes.
  145. return bin_to_hex(self.binsha).decode("ascii")
  146. @property
  147. def data_stream(self) -> "OStream":
  148. """
  149. :return:
  150. File-object compatible stream to the uncompressed raw data of the object
  151. :note:
  152. Returned streams must be read in order.
  153. """
  154. return self.repo.odb.stream(self.binsha)
  155. def stream_data(self, ostream: "OStream") -> "Object":
  156. """Write our data directly to the given output stream.
  157. :param ostream:
  158. File-object compatible stream object.
  159. :return:
  160. self
  161. """
  162. istream = self.repo.odb.stream(self.binsha)
  163. stream_copy(istream, ostream)
  164. return self
  165. class IndexObject(Object):
  166. """Base for all objects that can be part of the index file.
  167. The classes representing git object types that can be part of the index file are
  168. :class:`~git.objects.tree.Tree` and :class:`~git.objects.blob.Blob`. In addition,
  169. :class:`~git.objects.submodule.base.Submodule`, which is not really a git object
  170. type but can be part of an index file, is also a subclass.
  171. """
  172. __slots__ = ("path", "mode")
  173. # For compatibility with iterable lists.
  174. _id_attribute_ = "path"
  175. def __init__(
  176. self,
  177. repo: "Repo",
  178. binsha: bytes,
  179. mode: Union[None, int] = None,
  180. path: Union[None, PathLike] = None,
  181. ) -> None:
  182. """Initialize a newly instanced :class:`IndexObject`.
  183. :param repo:
  184. The :class:`~git.repo.base.Repo` we are located in.
  185. :param binsha:
  186. 20 byte sha1.
  187. :param mode:
  188. The stat-compatible file mode as :class:`int`.
  189. Use the :mod:`stat` module to evaluate the information.
  190. :param path:
  191. The path to the file in the file system, relative to the git repository
  192. root, like ``file.ext`` or ``folder/other.ext``.
  193. :note:
  194. Path may not be set if the index object has been created directly, as it
  195. cannot be retrieved without knowing the parent tree.
  196. """
  197. super().__init__(repo, binsha)
  198. if mode is not None:
  199. self.mode = mode
  200. if path is not None:
  201. self.path = path
  202. def __hash__(self) -> int:
  203. """
  204. :return:
  205. Hash of our path as index items are uniquely identifiable by path, not by
  206. their data!
  207. """
  208. return hash(self.path)
  209. def _set_cache_(self, attr: str) -> None:
  210. if attr in IndexObject.__slots__:
  211. # They cannot be retrieved later on (not without searching for them).
  212. raise AttributeError(
  213. "Attribute '%s' unset: path and mode attributes must have been set during %s object creation"
  214. % (attr, type(self).__name__)
  215. )
  216. else:
  217. super()._set_cache_(attr)
  218. # END handle slot attribute
  219. @property
  220. def name(self) -> str:
  221. """:return: Name portion of the path, effectively being the basename"""
  222. return osp.basename(self.path)
  223. @property
  224. def abspath(self) -> PathLike:
  225. R"""
  226. :return:
  227. Absolute path to this index object in the file system (as opposed to the
  228. :attr:`path` field which is a path relative to the git repository).
  229. The returned path will be native to the system and contains ``\`` on
  230. Windows.
  231. """
  232. if self.repo.working_tree_dir is not None:
  233. return join_path_native(self.repo.working_tree_dir, self.path)
  234. else:
  235. raise WorkTreeRepositoryUnsupported("working_tree_dir was None or empty")