Skip to content

Commit d3804b8

Browse files
committed
Return Python enums from _pygit2
Affected functions: - Repository.status - Repository.status_file - Repository.merge_analysis - DiffFile.flags - DiffFile.mode - DiffDelta.flags - DiffDelta.status
1 parent ea36ef1 commit d3804b8

File tree

8 files changed

+214
-33
lines changed

8 files changed

+214
-33
lines changed

pygit2/__init__.py

+5
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@
5959
# libgit version tuple
6060
LIBGIT2_VER = (LIBGIT2_VER_MAJOR, LIBGIT2_VER_MINOR, LIBGIT2_VER_REVISION)
6161

62+
# Let _pygit2 cache references to Python enum types.
63+
# This is separate from PyInit__pygit2() to avoid a circular import.
64+
cache_enums()
65+
del cache_enums # Don't expose this to user code
66+
6267

6368
def init_repository(
6469
path: typing.Union[str, bytes, PathLike, None],

pygit2/_pygit2.pyi

+22-17
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
11
from typing import Iterator, Literal, Optional, overload
22
from io import IOBase
33
from . import Index, Submodule
4-
from .enums import ApplyLocation
5-
from .enums import BranchType
6-
from .enums import DiffFind
7-
from .enums import DiffOption
8-
from .enums import DiffStatsFormat
9-
from .enums import MergeAnalysis
10-
from .enums import MergePreference
11-
from .enums import ObjectType
12-
from .enums import Option
13-
from .enums import ReferenceFilter
14-
from .enums import ResetMode
15-
from .enums import SortMode
4+
from .enums import (
5+
ApplyLocation,
6+
BranchType,
7+
DiffFind,
8+
DiffFlag,
9+
DiffOption,
10+
DiffStatsFormat,
11+
FileMode,
12+
FileStatus,
13+
MergeAnalysis,
14+
MergePreference,
15+
ObjectType,
16+
Option,
17+
ReferenceFilter,
18+
ResetMode,
19+
SortMode,
20+
)
1621

1722
GIT_OBJ_BLOB: Literal[3]
1823
GIT_OBJ_COMMIT: Literal[1]
@@ -139,19 +144,19 @@ class Diff:
139144
def __len__(self) -> int: ...
140145

141146
class DiffDelta:
142-
flags: int
147+
flags: DiffFlag
143148
is_binary: bool
144-
new_file: DiffFile
145149
nfiles: int
150+
new_file: DiffFile
146151
old_file: DiffFile
147152
similarity: int
148-
status: int
153+
status: FileStatus
149154
def status_char(self) -> str: ...
150155

151156
class DiffFile:
152-
flags: int
157+
flags: DiffFlag
153158
id: Oid
154-
mode: int
159+
mode: FileMode
155160
path: str
156161
raw_path: bytes
157162
size: int

src/diff.c

+53-5
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ extern PyTypeObject DiffLineType;
4747
extern PyTypeObject DiffStatsType;
4848
extern PyTypeObject RepositoryType;
4949

50+
extern PyObject *DeltaStatusEnum;
51+
extern PyObject *DiffFlagEnum;
52+
extern PyObject *FileModeEnum;
53+
5054
PyObject *
5155
wrap_diff(git_diff *diff, Repository *repo)
5256
{
@@ -204,13 +208,31 @@ DiffFile_from_c(DiffFile *dummy, PyObject *py_diff_file_ptr)
204208
return wrap_diff_file(diff_file);
205209
}
206210

211+
PyDoc_STRVAR(DiffFile_flags__doc__,
212+
"A combination of enums.DiffFlag constants."
213+
);
214+
215+
PyObject *
216+
DiffFile_flags__get__(DiffFile *self)
217+
{
218+
return pygit2_enum(DiffFlagEnum, self->flags);
219+
}
220+
221+
PyDoc_STRVAR(DiffFile_mode__doc__,
222+
"Mode of the entry (an enums.FileMode constant)."
223+
);
224+
225+
PyObject *
226+
DiffFile_mode__get__(DiffFile *self)
227+
{
228+
return pygit2_enum(FileModeEnum, self->mode);
229+
}
230+
207231
PyMemberDef DiffFile_members[] = {
208232
MEMBER(DiffFile, id, T_OBJECT, "Oid of the item."),
209233
MEMBER(DiffFile, path, T_STRING, "Path to the entry."),
210234
MEMBER(DiffFile, raw_path, T_OBJECT, "Path to the entry (bytes)."),
211235
MEMBER(DiffFile, size, T_LONG, "Size of the entry."),
212-
MEMBER(DiffFile, flags, T_UINT, "Combination of GIT_DIFF_FLAG_* flags."),
213-
MEMBER(DiffFile, mode, T_USHORT, "Mode of the entry."),
214236
{NULL}
215237
};
216238

@@ -219,6 +241,12 @@ PyMethodDef DiffFile_methods[] = {
219241
{NULL},
220242
};
221243

244+
PyGetSetDef DiffFile_getsetters[] = {
245+
GETTER(DiffFile, flags),
246+
GETTER(DiffFile, mode),
247+
{NULL},
248+
};
249+
222250
PyDoc_STRVAR(DiffFile__doc__, "DiffFile object.");
223251

224252
PyTypeObject DiffFileType = {
@@ -251,7 +279,7 @@ PyTypeObject DiffFileType = {
251279
0, /* tp_iternext */
252280
DiffFile_methods, /* tp_methods */
253281
DiffFile_members, /* tp_members */
254-
0, /* tp_getset */
282+
DiffFile_getsetters, /* tp_getset */
255283
0, /* tp_base */
256284
0, /* tp_dict */
257285
0, /* tp_descr_get */
@@ -294,6 +322,26 @@ DiffDelta_is_binary__get__(DiffDelta *self)
294322
Py_RETURN_NONE;
295323
}
296324

325+
PyDoc_STRVAR(DiffDelta_status__doc__,
326+
"An enums.FileStatus constant."
327+
);
328+
329+
PyObject *
330+
DiffDelta_status__get__(DiffDelta *self)
331+
{
332+
return pygit2_enum(DeltaStatusEnum, self->status);
333+
}
334+
335+
PyDoc_STRVAR(DiffDelta_flags__doc__,
336+
"A combination of enums.DiffFlag constants."
337+
);
338+
339+
PyObject *
340+
DiffDelta_flags__get__(DiffDelta *self)
341+
{
342+
return pygit2_enum(DiffFlagEnum, self->flags);
343+
}
344+
297345
static void
298346
DiffDelta_dealloc(DiffDelta *self)
299347
{
@@ -308,8 +356,6 @@ static PyMethodDef DiffDelta_methods[] = {
308356
};
309357

310358
PyMemberDef DiffDelta_members[] = {
311-
MEMBER(DiffDelta, status, T_UINT, "A GIT_DELTA_* constant."),
312-
MEMBER(DiffDelta, flags, T_UINT, "Combination of GIT_DIFF_FLAG_* flags."),
313359
MEMBER(DiffDelta, similarity, T_USHORT, "For renamed and copied."),
314360
MEMBER(DiffDelta, nfiles, T_USHORT, "Number of files in the delta."),
315361
MEMBER(DiffDelta, old_file, T_OBJECT, "\"from\" side of the diff."),
@@ -319,6 +365,8 @@ PyMemberDef DiffDelta_members[] = {
319365

320366
PyGetSetDef DiffDelta_getsetters[] = {
321367
GETTER(DiffDelta, is_binary),
368+
GETTER(DiffDelta, status),
369+
GETTER(DiffDelta, flags),
322370
{NULL}
323371
};
324372

src/pygit2.c

+77-2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ PyObject *GitError;
4242
PyObject *AlreadyExistsError;
4343
PyObject *InvalidSpecError;
4444

45+
PyObject *DeltaStatusEnum;
46+
PyObject *DiffFlagEnum;
47+
PyObject *FileModeEnum;
48+
PyObject *FileStatusEnum;
49+
PyObject *MergeAnalysisEnum;
50+
PyObject *MergePreferenceEnum;
51+
4552
extern PyTypeObject RepositoryType;
4653
extern PyTypeObject OdbType;
4754
extern PyTypeObject OdbBackendType;
@@ -361,6 +368,74 @@ filter_unregister(PyObject *self, PyObject *args)
361368
}
362369

363370

371+
static void
372+
forget_enums(void)
373+
{
374+
Py_XDECREF(DeltaStatusEnum);
375+
Py_XDECREF(DiffFlagEnum);
376+
Py_XDECREF(FileModeEnum);
377+
Py_XDECREF(FileStatusEnum);
378+
Py_XDECREF(MergeAnalysisEnum);
379+
Py_XDECREF(MergePreferenceEnum);
380+
381+
DeltaStatusEnum = NULL;
382+
DiffFlagEnum = NULL;
383+
FileModeEnum = NULL;
384+
FileStatusEnum = NULL;
385+
MergeAnalysisEnum = NULL;
386+
MergePreferenceEnum = NULL;
387+
}
388+
389+
PyDoc_STRVAR(cache_enums__doc__,
390+
"cache_enums()\n"
391+
"\n"
392+
"For internal use only. Do not call this from user code.\n"
393+
"\n"
394+
"Let the _pygit2 C module cache references to Python enums\n"
395+
"defined in pygit2.enums.\n");
396+
397+
PyObject *
398+
cache_enums(PyObject *self, PyObject *args)
399+
{
400+
(void) args;
401+
402+
/* In case this is somehow being called several times, let go of old references */
403+
forget_enums();
404+
405+
PyObject *enums = PyImport_ImportModule("pygit2.enums");
406+
if (enums == NULL) {
407+
return NULL;
408+
}
409+
410+
#define CACHE_PYGIT2_ENUM(name) do { \
411+
name##Enum = PyObject_GetAttrString(enums, #name); \
412+
if (name##Enum == NULL) { goto fail; } \
413+
} while (0)
414+
415+
CACHE_PYGIT2_ENUM(DeltaStatus);
416+
CACHE_PYGIT2_ENUM(DiffFlag);
417+
CACHE_PYGIT2_ENUM(FileMode);
418+
CACHE_PYGIT2_ENUM(FileStatus);
419+
CACHE_PYGIT2_ENUM(MergeAnalysis);
420+
CACHE_PYGIT2_ENUM(MergePreference);
421+
422+
#undef CACHE_PYGIT2_ENUM
423+
424+
Py_RETURN_NONE;
425+
426+
fail:
427+
Py_DECREF(enums);
428+
forget_enums();
429+
return NULL;
430+
}
431+
432+
void
433+
free_module(void *self)
434+
{
435+
forget_enums();
436+
}
437+
438+
364439
PyMethodDef module_methods[] = {
365440
{"discover_repository", discover_repository, METH_VARARGS, discover_repository__doc__},
366441
{"hash", hash, METH_VARARGS, hash__doc__},
@@ -371,10 +446,10 @@ PyMethodDef module_methods[] = {
371446
{"tree_entry_cmp", tree_entry_cmp, METH_VARARGS, tree_entry_cmp__doc__},
372447
{"filter_register", (PyCFunction)filter_register, METH_VARARGS | METH_KEYWORDS, filter_register__doc__},
373448
{"filter_unregister", filter_unregister, METH_VARARGS, filter_unregister__doc__},
449+
{"cache_enums", cache_enums, METH_NOARGS, cache_enums__doc__},
374450
{NULL}
375451
};
376452

377-
378453
struct PyModuleDef moduledef = {
379454
PyModuleDef_HEAD_INIT,
380455
"_pygit2", /* m_name */
@@ -384,7 +459,7 @@ struct PyModuleDef moduledef = {
384459
NULL, /* m_reload */
385460
NULL, /* m_traverse */
386461
NULL, /* m_clear */
387-
NULL, /* m_free */
462+
free_module, /* m_free */
388463
};
389464

390465
PyMODINIT_FUNC

src/repository.c

+27-5
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ extern PyTypeObject NoteIterType;
6565
extern PyTypeObject StashType;
6666
extern PyTypeObject RefsIteratorType;
6767

68+
extern PyObject *FileStatusEnum;
69+
extern PyObject *MergeAnalysisEnum;
70+
extern PyObject *MergePreferenceEnum;
71+
6872
/* forward-declaration for Repsository._from_c() */
6973
PyTypeObject RepositoryType;
7074

@@ -705,7 +709,22 @@ Repository_merge_analysis(Repository *self, PyObject *args)
705709
goto out;
706710
}
707711

708-
py_result = Py_BuildValue("(ii)", analysis, preference);
712+
// Convert analysis to MergeAnalysis enum
713+
PyObject *analysis_enum = pygit2_enum(MergeAnalysisEnum, analysis);
714+
if (!analysis_enum) {
715+
py_result = NULL;
716+
goto out;
717+
}
718+
719+
// Convert preference to MergePreference enum
720+
PyObject *preference_enum = pygit2_enum(MergePreferenceEnum, preference);
721+
if (!preference_enum) {
722+
py_result = NULL;
723+
Py_DECREF(analysis_enum);
724+
goto out;
725+
}
726+
727+
py_result = Py_BuildValue("(OO)", analysis_enum, preference_enum);
709728

710729
out:
711730
git_reference_free(our_ref);
@@ -1754,14 +1773,17 @@ Repository_status(Repository *self, PyObject *args, PyObject *kw)
17541773
path = entry->head_to_index->old_file.path;
17551774
else
17561775
path = entry->index_to_workdir->old_file.path;
1757-
status = PyLong_FromLong((long) entry->status);
1776+
1777+
/* Get corresponding entry in enums.FileStatus for status int */
1778+
status = pygit2_enum(FileStatusEnum, entry->status);
1779+
if (status == NULL)
1780+
goto error;
17581781

17591782
err = PyDict_SetItemString(dict, path, status);
17601783
Py_CLEAR(status);
17611784

17621785
if (err < 0)
17631786
goto error;
1764-
17651787
}
17661788

17671789
git_status_list_free(list);
@@ -1775,7 +1797,7 @@ Repository_status(Repository *self, PyObject *args, PyObject *kw)
17751797

17761798

17771799
PyDoc_STRVAR(Repository_status_file__doc__,
1778-
"status_file(path: str) -> int\n"
1800+
"status_file(path: str) -> enums.FileStatus\n"
17791801
"\n"
17801802
"Returns the status of the given file path.");
17811803

@@ -1795,7 +1817,7 @@ Repository_status_file(Repository *self, PyObject *value)
17951817
}
17961818
free(path);
17971819

1798-
return PyLong_FromLong(status);
1820+
return pygit2_enum(FileStatusEnum, (int) status);
17991821
}
18001822

18011823

src/utils.c

+15
Original file line numberDiff line numberDiff line change
@@ -276,3 +276,18 @@ py_object_to_otype(PyObject *py_type)
276276
PyErr_SetString(PyExc_ValueError, "invalid target type");
277277
return GIT_OBJECT_INVALID; /* -1 */
278278
}
279+
280+
281+
/**
282+
* Convert an integer to a reference to an IntEnum or IntFlag in pygit2.enums.
283+
*/
284+
PyObject *
285+
pygit2_enum(PyObject *enum_type, int value)
286+
{
287+
if (!enum_type) {
288+
PyErr_SetString(PyExc_TypeError, "an enum has not been cached in _pygit2.cache_enums()");
289+
return NULL;
290+
}
291+
PyObject *enum_instance = PyObject_CallFunction(enum_type, "(i)", value);
292+
return enum_instance;
293+
}

src/utils.h

+5
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ char* pgit_encode_fsdefault(PyObject *value);
9999

100100
git_otype py_object_to_otype(PyObject *py_type);
101101

102+
103+
/* Enum utilities (pygit2.enums) */
104+
PyObject *pygit2_enum(PyObject *enum_type, int value);
105+
106+
102107
/* Helpers to make shorter PyMethodDef and PyGetSetDef blocks */
103108
#define METHOD(type, name, args)\
104109
{#name, (PyCFunction) type ## _ ## name, args, type ## _ ## name ## __doc__}

0 commit comments

Comments
 (0)