1 /*
2  * Copyright 2010-2021 The pygit2 contributors
3  *
4  * This file is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License, version 2,
6  * as published by the Free Software Foundation.
7  *
8  * In addition to the permissions in the GNU General Public License,
9  * the authors give you unlimited permission to link the compiled
10  * version of this file into combinations with other programs,
11  * and to distribute those combinations without any restriction
12  * coming from the use of this file.  (The General Public License
13  * restrictions do apply in other respects; for example, they cover
14  * modification of the file, and distribution when not linked into
15  * a combined executable.)
16  *
17  * This file is distributed in the hope that it will be useful, but
18  * WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20  * General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; see the file COPYING.  If not, write to
24  * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
25  * Boston, MA 02110-1301, USA.
26  */
27 
28 #define PY_SSIZE_T_CLEAN
29 #include <Python.h>
30 #include <git2.h>
31 #include "error.h"
32 #include "types.h"
33 #include "utils.h"
34 #include "odb.h"
35 #include "oid.h"
36 #include "repository.h"
37 #include "object.h"
38 
39 extern PyTypeObject TreeType;
40 extern PyTypeObject CommitType;
41 extern PyTypeObject BlobType;
42 extern PyTypeObject TagType;
43 
44 PyTypeObject ObjectType;
45 
46 void
Object_dealloc(Object * self)47 Object_dealloc(Object* self)
48 {
49     Py_CLEAR(self->repo);
50     git_object_free(self->obj);
51     git_tree_entry_free((git_tree_entry*)self->entry);
52     Py_TYPE(self)->tp_free(self);
53 }
54 
55 
56 git_object*
Object__load(Object * self)57 Object__load(Object *self)
58 {
59     if (self->obj == NULL) {
60         int err = git_tree_entry_to_object(&self->obj, self->repo->repo, self->entry);
61         if (err < 0) {
62             Error_set(err);
63             return NULL;
64         }
65     }
66 
67     return self->obj;
68 }
69 
70 const git_oid*
Object__id(Object * self)71 Object__id(Object *self)
72 {
73     return (self->obj) ? git_object_id(self->obj) : git_tree_entry_id(self->entry);
74 }
75 
76 
77 git_object_t
Object__type(Object * self)78 Object__type(Object *self)
79 {
80     return (self->obj) ? git_object_type(self->obj) : git_tree_entry_type(self->entry);
81 }
82 
83 
84 PyDoc_STRVAR(Object_id__doc__,
85     "The object id, an instance of the Oid type.");
86 
87 PyObject *
Object_id__get__(Object * self)88 Object_id__get__(Object *self)
89 {
90     return git_oid_to_python(Object__id(self));
91 }
92 
93 PyDoc_STRVAR(Object_oid__doc__,
94     "The object id, an instance of the Oid type.\n"
95     "This attribute is deprecated, please use 'id'\n");
96 
97 PyObject *
Object_oid__get__(Object * self)98 Object_oid__get__(Object *self)
99 {
100     return Object_id__get__(self);
101 }
102 
103 PyDoc_STRVAR(Object_hex__doc__,
104     "Hexadecimal representation of the object id. This is a shortcut for\n"
105     "Object.oid.hex\n"
106     "This attribute is deprecated, please use 'id'\n");
107 
108 PyObject *
Object_hex__get__(Object * self)109 Object_hex__get__(Object *self)
110 {
111     return git_oid_to_py_str(Object__id(self));
112 }
113 
114 
115 PyDoc_STRVAR(Object_short_id__doc__,
116     "An unambiguous short (abbreviated) hex Oid string for the object.");
117 
118 PyObject *
Object_short_id__get__(Object * self)119 Object_short_id__get__(Object *self)
120 {
121     if (Object__load(self) == NULL) { return NULL; } // Lazy load
122 
123     git_buf short_id = { NULL, 0, 0 };
124     int err = git_object_short_id(&short_id, self->obj);
125     if (err != GIT_OK)
126         return Error_set(err);
127 
128     PyObject *py_short_id = to_unicode_n(short_id.ptr, short_id.size, NULL, "strict");
129     git_buf_dispose(&short_id);
130     return py_short_id;
131 }
132 
133 
134 PyDoc_STRVAR(Object_type__doc__,
135     "One of the GIT_OBJ_COMMIT, GIT_OBJ_TREE, GIT_OBJ_BLOB or GIT_OBJ_TAG\n"
136     "constants.");
137 
138 PyObject *
Object_type__get__(Object * self)139 Object_type__get__(Object *self)
140 {
141     return PyLong_FromLong(Object__type(self));
142 }
143 
144 PyDoc_STRVAR(Object_type_str__doc__,
145     "One of the 'commit', 'tree', 'blob' or 'tag' strings.");
146 
147 PyObject *
Object_type_str__get__(Object * self)148 Object_type_str__get__(Object *self)
149 {
150     return to_path(git_object_type2string(Object__type(self)));
151 }
152 
153 PyDoc_STRVAR(Object__pointer__doc__, "Get the object's pointer. For internal use only.");
154 PyObject *
Object__pointer__get__(Object * self)155 Object__pointer__get__(Object *self)
156 {
157     /* Bytes means a raw buffer */
158     if (Object__load(self) == NULL) { return NULL; } // Lazy load
159     return PyBytes_FromStringAndSize((char *) &self->obj, sizeof(git_object *));
160 }
161 
162 PyDoc_STRVAR(Object_name__doc__,
163              "Name (will be None if the object was not reached trough a tree)");
164 PyObject *
Object_name__get__(Object * self)165 Object_name__get__(Object *self)
166 {
167     if (self->entry == NULL)
168         Py_RETURN_NONE;
169 
170     return to_path(git_tree_entry_name(self->entry));
171 }
172 
173 PyDoc_STRVAR(Object_raw_name__doc__, "Name (bytes).");
174 
175 PyObject *
Object_raw_name__get__(Object * self)176 Object_raw_name__get__(Object *self)
177 {
178     if (self->entry == NULL)
179         Py_RETURN_NONE;
180 
181     return PyBytes_FromString(git_tree_entry_name(self->entry));
182 }
183 
184 PyDoc_STRVAR(Object_filemode__doc__,
185              "Filemode (will be None if the object was not reached trough a tree)");
186 PyObject *
Object_filemode__get__(Object * self)187 Object_filemode__get__(Object *self)
188 {
189     if (self->entry == NULL)
190         Py_RETURN_NONE;
191 
192     return PyLong_FromLong(git_tree_entry_filemode(self->entry));
193 }
194 
195 
196 PyDoc_STRVAR(Object_read_raw__doc__,
197   "read_raw()\n"
198   "\n"
199   "Returns the byte string with the raw contents of the object.");
200 
201 PyObject *
Object_read_raw(Object * self)202 Object_read_raw(Object *self)
203 {
204     int err;
205     git_odb *odb;
206     PyObject *aux;
207 
208     err = git_repository_odb(&odb, self->repo->repo);
209     if (err < 0)
210         return Error_set(err);
211 
212     const git_oid *oid = Object__id(self);
213     git_odb_object *obj = Odb_read_raw(odb, oid, GIT_OID_HEXSZ);
214     git_odb_free(odb);
215     if (obj == NULL)
216         return NULL;
217 
218     aux = PyBytes_FromStringAndSize(
219         git_odb_object_data(obj),
220         git_odb_object_size(obj));
221 
222     git_odb_object_free(obj);
223     return aux;
224 }
225 
226 PyDoc_STRVAR(Object_peel__doc__,
227   "peel(target_type) -> Object\n"
228   "\n"
229   "Peel the current object and returns the first object of the given type\n");
230 
231 PyObject *
Object_peel(Object * self,PyObject * py_type)232 Object_peel(Object *self, PyObject *py_type)
233 {
234     int err;
235     git_otype otype;
236     git_object *peeled;
237 
238     if (Object__load(self) == NULL) { return NULL; } // Lazy load
239 
240     otype = py_object_to_otype(py_type);
241     if (otype == GIT_OBJ_BAD)
242         return NULL;
243 
244     err = git_object_peel(&peeled, self->obj, otype);
245     if (err < 0)
246         return Error_set(err);
247 
248     return wrap_object(peeled, self->repo, NULL);
249 }
250 
251 Py_hash_t
Object_hash(Object * self)252 Object_hash(Object *self)
253 {
254     const git_oid *oid = Object__id(self);
255     PyObject *py_oid = git_oid_to_py_str(oid);
256     Py_hash_t ret = PyObject_Hash(py_oid);
257     Py_DECREF(py_oid);
258     return ret;
259 }
260 
261 PyObject *
Object_repr(Object * self)262 Object_repr(Object *self)
263 {
264     char hex[GIT_OID_HEXSZ + 1];
265 
266     git_oid_fmt(hex, Object__id(self));
267     hex[GIT_OID_HEXSZ] = '\0';
268 
269     return PyUnicode_FromFormat("<pygit2.Object{%s:%s}>",
270         git_object_type2string(Object__type(self)),
271         hex
272     );
273 }
274 
275 PyObject *
Object_richcompare(PyObject * o1,PyObject * o2,int op)276 Object_richcompare(PyObject *o1, PyObject *o2, int op)
277 {
278     PyObject *res;
279 
280     if (!PyObject_TypeCheck(o2, &ObjectType)) {
281         Py_INCREF(Py_NotImplemented);
282         return Py_NotImplemented;
283     }
284 
285     int equal = git_oid_equal(Object__id((Object *)o1), Object__id((Object *)o2));
286     switch (op) {
287         case Py_NE:
288             res = (equal) ? Py_False : Py_True;
289             break;
290         case Py_EQ:
291             res = (equal) ? Py_True : Py_False;
292             break;
293         case Py_LT:
294         case Py_LE:
295         case Py_GT:
296         case Py_GE:
297             Py_INCREF(Py_NotImplemented);
298             return Py_NotImplemented;
299         default:
300             PyErr_Format(PyExc_RuntimeError, "Unexpected '%d' op", op);
301             return NULL;
302     }
303 
304     Py_INCREF(res);
305     return res;
306 }
307 
308 PyGetSetDef Object_getseters[] = {
309     GETTER(Object, oid),
310     GETTER(Object, id),
311     GETTER(Object, hex),
312     GETTER(Object, short_id),
313     GETTER(Object, type),
314     GETTER(Object, type_str),
315     GETTER(Object, _pointer),
316     // These come from git_tree_entry
317     GETTER(Object, name),
318     GETTER(Object, raw_name),
319     GETTER(Object, filemode),
320     {NULL}
321 };
322 
323 PyMethodDef Object_methods[] = {
324     METHOD(Object, read_raw, METH_NOARGS),
325     METHOD(Object, peel, METH_O),
326     {NULL}
327 };
328 
329 
330 PyDoc_STRVAR(Object__doc__, "Base class for Git objects.");
331 
332 PyTypeObject ObjectType = {
333     PyVarObject_HEAD_INIT(NULL, 0)
334     "_pygit2.Object",                          /* tp_name           */
335     sizeof(Object),                            /* tp_basicsize      */
336     0,                                         /* tp_itemsize       */
337     (destructor)Object_dealloc,                /* tp_dealloc        */
338     0,                                         /* tp_print          */
339     0,                                         /* tp_getattr        */
340     0,                                         /* tp_setattr        */
341     0,                                         /* tp_compare        */
342     (reprfunc)Object_repr,                     /* tp_repr           */
343     0,                                         /* tp_as_number      */
344     0,                                         /* tp_as_sequence    */
345     0,                                         /* tp_as_mapping     */
346     (hashfunc)Object_hash,                     /* tp_hash           */
347     0,                                         /* tp_call           */
348     0,                                         /* tp_str            */
349     0,                                         /* tp_getattro       */
350     0,                                         /* tp_setattro       */
351     0,                                         /* tp_as_buffer      */
352     Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,  /* tp_flags          */
353     Object__doc__,                             /* tp_doc            */
354     0,                                         /* tp_traverse       */
355     0,                                         /* tp_clear          */
356     (richcmpfunc)Object_richcompare,           /* tp_richcompare    */
357     0,                                         /* tp_weaklistoffset */
358     0,                                         /* tp_iter           */
359     0,                                         /* tp_iternext       */
360     Object_methods,                            /* tp_methods        */
361     0,                                         /* tp_members        */
362     Object_getseters,                          /* tp_getset         */
363     0,                                         /* tp_base           */
364     0,                                         /* tp_dict           */
365     0,                                         /* tp_descr_get      */
366     0,                                         /* tp_descr_set      */
367     0,                                         /* tp_dictoffset     */
368     0,                                         /* tp_init           */
369     0,                                         /* tp_alloc          */
370     0,                                         /* tp_new            */
371 };
372 
373 PyObject *
wrap_object(git_object * c_object,Repository * repo,const git_tree_entry * entry)374 wrap_object(git_object *c_object, Repository *repo, const git_tree_entry *entry)
375 {
376     Object *py_obj = NULL;
377 
378     git_object_t obj_type = (c_object) ? git_object_type(c_object) : git_tree_entry_type(entry);
379 
380     switch (obj_type) {
381         case GIT_OBJ_COMMIT:
382             py_obj = PyObject_New(Object, &CommitType);
383             break;
384         case GIT_OBJ_TREE:
385             py_obj = PyObject_New(Object, &TreeType);
386             break;
387         case GIT_OBJ_BLOB:
388             py_obj = PyObject_New(Object, &BlobType);
389             break;
390         case GIT_OBJ_TAG:
391             py_obj = PyObject_New(Object, &TagType);
392             break;
393         default:
394             assert(0);
395     }
396 
397     if (py_obj) {
398         py_obj->obj = c_object;
399         if (repo) {
400             py_obj->repo = repo;
401             Py_INCREF(repo);
402         }
403         py_obj->entry = entry;
404     }
405     return (PyObject *)py_obj;
406 }
407