diff --git a/pygit2/_pygit2.pyi b/pygit2/_pygit2.pyi index a30a911a..19dd61ff 100644 --- a/pygit2/_pygit2.pyi +++ b/pygit2/_pygit2.pyi @@ -518,7 +518,8 @@ class Odb: def add_backend(self, backend: OdbBackend, priority: int) -> None: ... def add_disk_alternate(self, path: str | Path) -> None: ... def exists(self, oid: _OidArg) -> bool: ... - def read(self, oid: _OidArg) -> tuple[int, bytes]: ... + def read(self, oid: _OidArg) -> tuple[ObjectType, bytes]: ... + def read_header(self, oid: _OidArg) -> tuple[ObjectType, int]: ... def write(self, type: int, data: bytes | str) -> Oid: ... def __contains__(self, other: _OidArg) -> bool: ... def __iter__(self) -> Iterator[Oid]: ... # Odb_as_iter diff --git a/src/odb.c b/src/odb.c index 97181aa7..e72a099e 100644 --- a/src/odb.c +++ b/src/odb.c @@ -37,6 +37,8 @@ extern PyTypeObject OdbBackendType; +extern PyObject *ObjectTypeEnum; + static git_otype int_to_loose_object_type(int type_id) { @@ -170,7 +172,7 @@ Odb_read_raw(git_odb *odb, const git_oid *oid, size_t len) } PyDoc_STRVAR(Odb_read__doc__, - "read(oid) -> type, data, size\n" + "read(oid: Oid) -> tuple[enums.ObjectType, bytes]\n" "\n" "Read raw object data from the object db."); @@ -180,6 +182,8 @@ Odb_read(Odb *self, PyObject *py_hex) git_oid oid; git_odb_object *obj; size_t len; + git_object_t type; + PyObject* type_enum; PyObject* tuple; len = py_oid_to_git_oid(py_hex, &oid); @@ -190,9 +194,13 @@ Odb_read(Odb *self, PyObject *py_hex) if (obj == NULL) return NULL; + // Convert type to ObjectType enum + type = git_odb_object_type(obj); + type_enum = pygit2_enum(ObjectTypeEnum, type); + tuple = Py_BuildValue( - "(ny#)", - git_odb_object_type(obj), + "(Oy#)", + type_enum, git_odb_object_data(obj), git_odb_object_size(obj)); @@ -200,6 +208,44 @@ Odb_read(Odb *self, PyObject *py_hex) return tuple; } +PyDoc_STRVAR(Odb_read_header__doc__, + "read_header(oid: Oid) -> tuple[enums.ObjectType, size\n" + "\n" + "Read the header of an object from the database, without reading its full\n" + "contents.\n" + "\n" + "The header includes the type and the length of an object.\n" + "\n" + "Note that most backends do not support reading only the header of an object,\n" + "so the whole object may be read and then the header will be returned."); + +PyObject * +Odb_read_header(Odb *self, PyObject *py_hex) +{ + git_oid oid; + int err; + size_t len; + git_object_t type; + PyObject* type_enum; + PyObject* tuple; + + len = py_oid_to_git_oid(py_hex, &oid); + if (len == 0) + return NULL; + + err = git_odb_read_header(&len, &type, self->odb, &oid); + if (err != 0) { + Error_set_oid(err, &oid, len); + return NULL; + } + + // Convert type to ObjectType enum + type_enum = pygit2_enum(ObjectTypeEnum, type); + + tuple = Py_BuildValue("(On)", type_enum, len); + return tuple; +} + PyDoc_STRVAR(Odb_write__doc__, "write(type: int, data: bytes) -> Oid\n" "\n" @@ -301,6 +347,7 @@ Odb_add_backend(Odb *self, PyObject *args) PyMethodDef Odb_methods[] = { METHOD(Odb, add_disk_alternate, METH_O), METHOD(Odb, read, METH_O), + METHOD(Odb, read_header, METH_O), METHOD(Odb, write, METH_VARARGS), METHOD(Odb, exists, METH_O), METHOD(Odb, add_backend, METH_VARARGS), diff --git a/src/pygit2.c b/src/pygit2.c index 96cec5f1..91fb2cb4 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -47,6 +47,7 @@ PyObject *FileModeEnum; PyObject *FileStatusEnum; PyObject *MergeAnalysisEnum; PyObject *MergePreferenceEnum; +PyObject *ObjectTypeEnum; PyObject *ReferenceTypeEnum; extern PyTypeObject RepositoryType; @@ -354,6 +355,7 @@ forget_enums(void) Py_CLEAR(FileStatusEnum); Py_CLEAR(MergeAnalysisEnum); Py_CLEAR(MergePreferenceEnum); + Py_CLEAR(ObjectTypeEnum); Py_CLEAR(ReferenceTypeEnum); } @@ -389,6 +391,7 @@ _cache_enums(PyObject *self, PyObject *args) CACHE_PYGIT2_ENUM(FileStatus); CACHE_PYGIT2_ENUM(MergeAnalysis); CACHE_PYGIT2_ENUM(MergePreference); + CACHE_PYGIT2_ENUM(ObjectType); CACHE_PYGIT2_ENUM(ReferenceType); #undef CACHE_PYGIT2_ENUM diff --git a/test/test_odb.py b/test/test_odb.py index 4d023135..f0657af5 100644 --- a/test/test_odb.py +++ b/test/test_odb.py @@ -41,6 +41,7 @@ BLOB_HEX = 'af431f20fc541ed6d5afede3e2dc7160f6f01f16' BLOB_RAW = binascii.unhexlify(BLOB_HEX.encode('ascii')) BLOB_OID = Oid(raw=BLOB_RAW) +BLOB_CONTENTS = b'a contents\n' def test_emptyodb(barerepo: Repository) -> None: @@ -75,14 +76,29 @@ def test_read(odb: Odb) -> None: ab = odb.read(BLOB_OID) a = odb.read(BLOB_HEX) assert ab == a - assert (ObjectType.BLOB, b'a contents\n') == a + assert (ObjectType.BLOB, BLOB_CONTENTS) == a + assert isinstance(a[0], ObjectType) a2 = odb.read('7f129fd57e31e935c6d60a0c794efe4e6927664b') assert (ObjectType.BLOB, b'a contents 2\n') == a2 + assert isinstance(a2[0], ObjectType) a_hex_prefix = BLOB_HEX[:4] a3 = odb.read(a_hex_prefix) - assert (ObjectType.BLOB, b'a contents\n') == a3 + assert (ObjectType.BLOB, BLOB_CONTENTS) == a3 + assert isinstance(a3[0], ObjectType) + + +def test_read_header(odb: Odb) -> None: + with pytest.raises(TypeError): + odb.read_header(123) # type: ignore + utils.assertRaisesWithArg(KeyError, '1' * 40, odb.read_header, '1' * 40) + + ab = odb.read_header(BLOB_OID) + a = odb.read_header(BLOB_HEX) + assert ab == a + assert (ObjectType.BLOB, len(BLOB_CONTENTS)) == a + assert isinstance(a[0], ObjectType) def test_write(odb: Odb) -> None: