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