diff --git a/TODO.txt b/TODO.txt new file mode 100644 index 0000000..99d24f4 --- /dev/null +++ b/TODO.txt @@ -0,0 +1,6 @@ +Signature +========= +- Implement equality interface +- Return unicode for the email +- Implement interface to access the name/email as bytes +- In Repository's create_commit/create_tag check signatures encoding is right diff --git a/pygit2.c b/pygit2.c index 86ed7fc..6c3d32f 100644 --- a/pygit2.c +++ b/pygit2.c @@ -104,6 +104,13 @@ typedef struct { git_reference *reference; } Reference; +typedef struct { + PyObject_HEAD + Object *obj; + const git_signature *signature; + const char *encoding; +} Signature; + static PyTypeObject RepositoryType; static PyTypeObject ObjectType; static PyTypeObject CommitType; @@ -117,6 +124,7 @@ static PyTypeObject IndexIterType; static PyTypeObject IndexEntryType; static PyTypeObject WalkerType; static PyTypeObject ReferenceType; +static PyTypeObject SignatureType; static PyObject *GitError; @@ -614,47 +622,27 @@ Repository_walk(Repository *self, PyObject *args) } static PyObject * -build_person(const git_signature *signature, const char *encoding) +build_signature(Object *obj, const git_signature *signature, + const char *encoding) { - PyObject *name; + Signature *py_signature; - name = PyUnicode_Decode(signature->name, strlen(signature->name), - encoding, "strict"); - return Py_BuildValue("(NsLi)", name, signature->email, - signature->when.time, signature->when.offset); -} - -static git_signature * -py_signature_to_git_signature(PyObject *value, const char* encoding) -{ - PyObject *py_name; - char *name, *email; - long long time; - int offset; - int err; - git_signature *signature; - - if (!PyArg_ParseTuple(value, "OsLi", &py_name, &email, &time, &offset)) - return NULL; - - name = py_str_to_c_str(py_name, encoding); - - err = git_signature_new(&signature, name, email, time, offset); - if (err < 0) { - Error_set(err); - return NULL; + py_signature = PyObject_New(Signature, &SignatureType); + if (py_signature) { + Py_INCREF(obj); + py_signature->obj = obj; + py_signature->signature = signature; + py_signature->encoding = encoding; } - - return signature; + return (PyObject*)py_signature; } static PyObject * Repository_create_commit(Repository *self, PyObject *args) { - PyObject *py_author, *py_committer; + Signature *py_author, *py_committer; PyObject *py_oid, *py_message, *py_parents, *py_parent; PyObject *py_result = NULL; - git_signature *author = NULL, *committer = NULL; char *message, *update_ref, *encoding = NULL; git_oid oid; git_tree *tree = NULL; @@ -665,21 +653,14 @@ Repository_create_commit(Repository *self, PyObject *args) if (!PyArg_ParseTuple(args, "zO!O!OOO!|s", &update_ref, - &PyTuple_Type, &py_author, - &PyTuple_Type, &py_committer, + &SignatureType, &py_author, + &SignatureType, &py_committer, &py_message, &py_oid, &PyList_Type, &py_parents, &encoding)) return NULL; - author = py_signature_to_git_signature(py_author, encoding); - if (author == NULL) - return NULL; - committer = py_signature_to_git_signature(py_committer, encoding); - if (committer == NULL) - goto out; - len = py_str_to_git_oid(py_oid, &oid); if (len == 0) goto out; @@ -708,7 +689,8 @@ Repository_create_commit(Repository *self, PyObject *args) goto out; } - err = git_commit_create(&oid, self->repo, update_ref, author, committer, + err = git_commit_create(&oid, self->repo, update_ref, + py_author->signature, py_committer->signature, encoding, message, tree, parent_count, (const git_commit**)parents); if (err < 0) { @@ -719,8 +701,6 @@ Repository_create_commit(Repository *self, PyObject *args) py_result = git_oid_to_python(oid.id); out: - git_signature_free(author); - git_signature_free(committer); git_tree_close(tree); while (i > 0) { i--; @@ -733,9 +713,9 @@ out: static PyObject * Repository_create_tag(Repository *self, PyObject *args) { - PyObject *py_oid, *py_tagger, *py_result = NULL; + PyObject *py_oid, *py_result = NULL; + Signature *py_tagger; char *tag_name, *message; - git_signature *tagger = NULL; git_oid oid; git_object *target = NULL; int err, target_type; @@ -746,14 +726,10 @@ Repository_create_tag(Repository *self, PyObject *args) &tag_name, &py_oid, &target_type, - &PyTuple_Type, &py_tagger, + &SignatureType, &py_tagger, &message)) return NULL; - tagger = py_signature_to_git_signature(py_tagger, NULL); - if (tagger == NULL) - return NULL; - len = py_str_to_git_oid(py_oid, &oid); if (len == 0) goto out; @@ -767,13 +743,12 @@ Repository_create_tag(Repository *self, PyObject *args) goto out; } - err = git_tag_create(&oid, self->repo, tag_name, target, tagger, message, - 0); + err = git_tag_create(&oid, self->repo, tag_name, target, + py_tagger->signature, message, 0); if (err == 0) py_result = git_oid_to_python(oid.id); out: - git_signature_free(tagger); git_object_close(target); return py_result; } @@ -1187,29 +1162,27 @@ Commit_get_commit_time_offset(Commit *commit) } static PyObject * -Commit_get_committer(Commit *commit) +Commit_get_committer(Commit *self) { const git_signature *signature; const char *encoding; - signature = git_commit_committer(commit->commit); - encoding = git_commit_message_encoding(commit->commit); - if (encoding == NULL) - encoding = "utf-8"; - return build_person(signature, encoding); + signature = git_commit_committer(self->commit); + encoding = git_commit_message_encoding(self->commit); + + return build_signature((Object*)self, signature, encoding); } static PyObject * -Commit_get_author(Commit *commit) +Commit_get_author(Commit *self) { const git_signature *signature; const char *encoding; - signature = git_commit_author(commit->commit); - encoding = git_commit_message_encoding(commit->commit); - if (encoding == NULL) - encoding = "utf-8"; - return build_person(signature, encoding); + signature = git_commit_author(self->commit); + encoding = git_commit_message_encoding(self->commit); + + return build_signature((Object*)self, signature, encoding); } static PyObject * @@ -1715,12 +1688,13 @@ Tag_get_name(Tag *self) } static PyObject * -Tag_get_tagger(Tag *tag) +Tag_get_tagger(Tag *self) { - const git_signature *signature = git_tag_tagger(tag->tag); + const git_signature *signature = git_tag_tagger(self->tag); if (!signature) Py_RETURN_NONE; - return build_person(signature, "utf-8"); + + return build_signature((Object*)self, signature, "utf-8"); } static PyObject * @@ -2624,6 +2598,142 @@ static PyTypeObject ReferenceType = { 0, /* tp_new */ }; +static int +Signature_init(Signature *self, PyObject *args, PyObject *kwds) +{ + PyObject *py_name; + char *name, *email, *encoding = NULL; + long long time; + int offset; + int err; + git_signature *signature; + + if (kwds) { + PyErr_SetString(PyExc_TypeError, + "Signature takes no keyword arguments"); + return -1; + } + + if (!PyArg_ParseTuple(args, "OsLi|s", + &py_name, &email, &time, &offset, &encoding)) + return -1; + + name = py_str_to_c_str(py_name, encoding); + + err = git_signature_new(&signature, name, email, time, offset); + if (err < 0) { + Error_set(err); + return -1; + } + + self->obj = NULL; + self->signature = signature; + + if (encoding) { + self->encoding = strdup(encoding); + if (self->encoding == NULL) { + PyErr_NoMemory(); + return -1; + } + } + + return 0; +} + +static void +Signature_dealloc(Signature *self) +{ + if (self->obj) + Py_DECREF(self->obj); + else { + git_signature_free((git_signature*)self->signature); + free((void*)self->encoding); + } + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static PyObject * +Signature_get_name(Signature *self) +{ + const char *encoding; + git_object *object; + + encoding = self->encoding; + if (encoding == NULL) + encoding = "utf-8"; + + return PyUnicode_Decode(self->signature->name, + strlen(self->signature->name), + encoding, "strict"); +} + +static PyObject * +Signature_get_email(Signature *self) +{ + return PyString_FromString(self->signature->email); +} + +static PyObject * +Signature_get_time(Signature *self) +{ + return PyInt_FromLong(self->signature->when.time); +} + +static PyObject * +Signature_get_offset(Signature *self) +{ + return PyInt_FromLong(self->signature->when.offset); +} + +static PyGetSetDef Signature_getseters[] = { + {"name", (getter)Signature_get_name, NULL, "Name", NULL}, + {"email", (getter)Signature_get_email, NULL, "Email", NULL}, + {"time", (getter)Signature_get_time, NULL, "Time", NULL}, + {"offset", (getter)Signature_get_offset, NULL, "Offset", NULL}, + {NULL} +}; + +static PyTypeObject SignatureType = { + PyVarObject_HEAD_INIT(NULL, 0) + "pygit2.Signature", /* tp_name */ + sizeof(Signature), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)Signature_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + "Signature", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + Signature_getseters, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)Signature_init, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ +}; + static PyObject * init_repository(PyObject *self, PyObject *args) { @@ -2704,6 +2814,9 @@ moduleinit(PyObject* m) ReferenceType.tp_new = PyType_GenericNew; if (PyType_Ready(&ReferenceType) < 0) return NULL; + SignatureType.tp_new = PyType_GenericNew; + if (PyType_Ready(&SignatureType) < 0) + return NULL; Py_INCREF(GitError); PyModule_AddObject(m, "GitError", GitError); @@ -2738,6 +2851,9 @@ moduleinit(PyObject* m) Py_INCREF(&ReferenceType); PyModule_AddObject(m, "Reference", (PyObject *)&ReferenceType); + Py_INCREF(&SignatureType); + PyModule_AddObject(m, "Signature", (PyObject *)&SignatureType); + PyModule_AddIntConstant(m, "GIT_OBJ_ANY", GIT_OBJ_ANY); PyModule_AddIntConstant(m, "GIT_OBJ_COMMIT", GIT_OBJ_COMMIT); PyModule_AddIntConstant(m, "GIT_OBJ_TREE", GIT_OBJ_TREE); diff --git a/test/test_commit.py b/test/test_commit.py index 71716a2..526d7d1 100644 --- a/test/test_commit.py +++ b/test/test_commit.py @@ -31,7 +31,7 @@ from __future__ import absolute_import from __future__ import unicode_literals import unittest -from pygit2 import GIT_OBJ_COMMIT +from pygit2 import GIT_OBJ_COMMIT, Signature from . import utils @@ -55,20 +55,22 @@ class CommitTest(utils.BareRepoTestCase): commit.message) commit_time = 1288481576 self.assertEqual(commit_time, commit.commit_time) - self.assertEqual( - ('Dave Borowitz', 'dborowitz@google.com', commit_time, -420), - commit.committer) - self.assertEqual( - ('Dave Borowitz', 'dborowitz@google.com', 1288477363, -420), - commit.author) + self.assertEqualSignature( + commit.committer, + Signature('Dave Borowitz', 'dborowitz@google.com', + commit_time, -420)) + self.assertEqualSignature( + commit.author, + Signature('Dave Borowitz', 'dborowitz@google.com', 1288477363, + -420)) self.assertEqual( '967fce8df97cc71722d3c2a5930ef3e6f1d27b12', commit.tree.hex) def test_new_commit(self): repo = self.repo message = 'New commit.\n\nMessage with non-ascii chars: ééé.\n' - committer = ('John Doe', 'jdoe@example.com', 12346, 0) - author = ('J. David Ibáñez', 'jdavid@example.com', 12345, 0) + committer = Signature('John Doe', 'jdoe@example.com', 12346, 0) + author = Signature('J. David Ibáñez', 'jdavid@example.com', 12345, 0) tree = '967fce8df97cc71722d3c2a5930ef3e6f1d27b12' tree_prefix = tree[:5] too_short_prefix = tree[:3] @@ -87,31 +89,34 @@ class CommitTest(utils.BareRepoTestCase): self.assertEqual(None, commit.message_encoding) self.assertEqual(message, commit.message) self.assertEqual(12346, commit.commit_time) - self.assertEqual(committer, commit.committer) - self.assertEqual(author, commit.author) + self.assertEqualSignature(committer, commit.committer) + self.assertEqualSignature(author, commit.author) self.assertEqual(tree, commit.tree.hex) self.assertEqual(1, len(commit.parents)) self.assertEqual(COMMIT_SHA, commit.parents[0].hex) def test_new_commit_encoding(self): repo = self.repo + encoding = 'iso-8859-1' message = 'New commit.\n\nMessage with non-ascii chars: ééé.\n' - committer = ('John Doe', 'jdoe@example.com', 12346, 0) - author = ('J. David Ibáñez', 'jdavid@example.com', 12345, 0) + committer = Signature('John Doe', 'jdoe@example.com', 12346, 0, + encoding) + author = Signature('J. David Ibáñez', 'jdavid@example.com', 12345, 0, + encoding) tree = '967fce8df97cc71722d3c2a5930ef3e6f1d27b12' tree_prefix = tree[:5] parents = [COMMIT_SHA[:5]] sha = repo.create_commit(None, author, committer, message, - tree_prefix, parents, 'iso-8859-1') + tree_prefix, parents, encoding) commit = repo[sha] self.assertEqual(GIT_OBJ_COMMIT, commit.type) self.assertEqual('iso-8859-1', commit.message_encoding) self.assertEqual(message, commit.message) self.assertEqual(12346, commit.commit_time) - self.assertEqual(committer, commit.committer) - self.assertEqual(author, commit.author) + self.assertEqualSignature(committer, commit.committer) + self.assertEqualSignature(author, commit.author) self.assertEqual(tree, commit.tree.hex) self.assertEqual(1, len(commit.parents)) self.assertEqual(COMMIT_SHA, commit.parents[0].hex) diff --git a/test/test_tag.py b/test/test_tag.py index a138882..5c11e01 100644 --- a/test/test_tag.py +++ b/test/test_tag.py @@ -48,19 +48,18 @@ class TagTest(utils.BareRepoTestCase): self.assertEqual(pygit2.GIT_OBJ_TAG, tag.type) self.assertEqual(pygit2.GIT_OBJ_COMMIT, tag.target.type) self.assertEqual('root', tag.name) - self.assertEqual( - ('Dave Borowitz', 'dborowitz@google.com', 1288724692, -420), - tag.tagger) self.assertEqual('Tagged root commit.\n', tag.message) - - commit = tag.target - self.assertEqual('Initial test data commit.\n', commit.message) + self.assertEqual('Initial test data commit.\n', tag.target.message) + self.assertEqualSignature( + tag.tagger, + pygit2.Signature('Dave Borowitz', 'dborowitz@google.com', + 1288724692, -420)) def test_new_tag(self): name = 'thetag' target = 'af431f20fc541ed6d5afede3e2dc7160f6f01f16' message = 'Tag a blob.\n' - tagger = ('John Doe', 'jdoe@example.com', 12347, 0) + tagger = pygit2.Signature('John Doe', 'jdoe@example.com', 12347, 0) target_prefix = target[:5] too_short_prefix = target[:3] @@ -74,7 +73,7 @@ class TagTest(utils.BareRepoTestCase): self.assertEqual('3ee44658fd11660e828dfc96b9b5c5f38d5b49bb', tag.hex) self.assertEqual(name, tag.name) self.assertEqual(target, tag.target.hex) - self.assertEqual(tagger, tag.tagger) + self.assertEqualSignature(tagger, tag.tagger) self.assertEqual(message, tag.message) self.assertEqual(name, self.repo[tag.hex].name) diff --git a/test/utils.py b/test/utils.py index b9c7549..f56d29d 100644 --- a/test/utils.py +++ b/test/utils.py @@ -74,6 +74,13 @@ class NoRepoTestCase(unittest.TestCase): else: self.fail('%s(%r) not raised' % (exc_class.__name__, arg)) + def assertEqualSignature(self, a, b): + # XXX Remove this once equality test is supported by Signature + self.assertEqual(a.name, b.name) + self.assertEqual(a.email, b.email) + self.assertEqual(a.time, b.time) + self.assertEqual(a.offset, b.offset) + class BareRepoTestCase(NoRepoTestCase):