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