From: Dave Borowitz <[email protected]> This provides a backwards-compatible API with negligible memory and runtime overhead, and is especially easier to use when dealing with multiple TreeEntry objects in parallel.
For benchmarks and discussion, see: https://lists.launchpad.net/dulwich-users/msg00234.html Change-Id: Ief7f66642e8b8b6a3550e008999af71e9b4b5dcc --- dulwich/_objects.c | 27 +++++++++++++++++--- dulwich/misc.py | 54 +++++++++++++++++++++++++++++++++++++++++ dulwich/objects.py | 3 +- dulwich/tests/test_objects.py | 13 +++++++--- 4 files changed, 88 insertions(+), 9 deletions(-) diff --git a/dulwich/_objects.c b/dulwich/_objects.c index 47f4262..e4ab236 100644 --- a/dulwich/_objects.c +++ b/dulwich/_objects.c @@ -35,6 +35,8 @@ size_t strnlen(char *text, size_t maxlen) #define bytehex(x) (((x)<0xa)?('0'+(x)):('a'-0xa+(x))) +static PyObject *tree_entry_cls; + static PyObject *sha_to_pyhex(const unsigned char *sha) { char hexsha[41]; @@ -147,7 +149,8 @@ int cmp_tree_item(const void *_a, const void *_b) static void free_tree_items(struct tree_item *items, int num) { int i; for (i = 0; i < num; i++) { - Py_DECREF(items[i].tuple); + if (items[i].tuple != NULL) + Py_DECREF(items[i].tuple); } free(items); } @@ -205,8 +208,14 @@ static PyObject *py_sorted_tree_items(PyObject *self, PyObject *entries) } qsort_entries[i].name = PyString_AS_STRING(key); qsort_entries[i].mode = PyInt_AS_LONG(py_mode); - qsort_entries[i].tuple = PyTuple_Pack(3, key, py_int_mode, py_sha); + + qsort_entries[i].tuple = PyObject_CallFunctionObjArgs( + tree_entry_cls, key, py_int_mode, py_sha, NULL); Py_DECREF(py_int_mode); + if (qsort_entries[i].tuple == NULL) { + free_tree_items(qsort_entries, i); + return NULL; + } i++; } @@ -234,11 +243,21 @@ static PyMethodDef py_objects_methods[] = { { NULL, NULL, 0, NULL } }; -void init_objects(void) +PyMODINIT_FUNC +init_objects(void) { - PyObject *m; + PyObject *m, *misc_mod; m = Py_InitModule3("_objects", py_objects_methods, NULL); if (m == NULL) return; + + misc_mod = PyImport_ImportModule("dulwich.misc"); + if (misc_mod == NULL) + return; + + tree_entry_cls = PyObject_GetAttrString(misc_mod, "TreeEntry"); + Py_DECREF(misc_mod); + if (tree_entry_cls == NULL) + return; } diff --git a/dulwich/misc.py b/dulwich/misc.py index 3a10b25..e9e0f01 100644 --- a/dulwich/misc.py +++ b/dulwich/misc.py @@ -99,3 +99,57 @@ def unpack_from(fmt, buf, offset=0): except AttributeError: b = buf[offset:offset+struct.calcsize(fmt)] return struct.unpack(fmt, b) + + +try: + from collections import namedtuple + + TreeEntry = namedtuple('TreeEntry', ['path', 'mode', 'sha']) +except ImportError: + # Provide manual implementations of namedtuples for Python <2.5. + # If the class definitions change, be sure to keep these in sync by running + # namedtuple(..., verbose=True) in a recent Python and pasting the output. + + # Necessary globals go here. + _tuple = tuple + _property = property + from operator import itemgetter as _itemgetter + + class TreeEntry(tuple): + 'TreeEntry(path, mode, sha)' + + __slots__ = () + + _fields = ('path', 'mode', 'sha') + + def __new__(_cls, path, mode, sha): + return _tuple.__new__(_cls, (path, mode, sha)) + + @classmethod + def _make(cls, iterable, new=tuple.__new__, len=len): + 'Make a new TreeEntry object from a sequence or iterable' + result = new(cls, iterable) + if len(result) != 3: + raise TypeError('Expected 3 arguments, got %d' % len(result)) + return result + + def __repr__(self): + return 'TreeEntry(path=%r, mode=%r, sha=%r)' % self + + def _asdict(t): + 'Return a new dict which maps field names to their values' + return {'path': t[0], 'mode': t[1], 'sha': t[2]} + + def _replace(_self, **kwds): + 'Return a new TreeEntry object replacing specified fields with new values' + result = _self._make(map(kwds.pop, ('path', 'mode', 'sha'), _self)) + if kwds: + raise ValueError('Got unexpected field names: %r' % kwds.keys()) + return result + + def __getnewargs__(self): + return tuple(self) + + path = _property(_itemgetter(0)) + mode = _property(_itemgetter(1)) + sha = _property(_itemgetter(2)) diff --git a/dulwich/objects.py b/dulwich/objects.py index ea69ec1..e406770 100644 --- a/dulwich/objects.py +++ b/dulwich/objects.py @@ -39,6 +39,7 @@ from dulwich.errors import ( from dulwich.file import GitFile from dulwich.misc import ( make_sha, + TreeEntry, ) @@ -733,7 +734,7 @@ def sorted_tree_items(entries): mode = int(mode) if not isinstance(hexsha, str): raise TypeError('Expected a string for SHA, got %r' % hexsha) - yield name, mode, hexsha + yield TreeEntry(name, mode, hexsha) def cmp_entry((name1, value1), (name2, value2)): diff --git a/dulwich/tests/test_objects.py b/dulwich/tests/test_objects.py index 195cac0..4fd4f42 100644 --- a/dulwich/tests/test_objects.py +++ b/dulwich/tests/test_objects.py @@ -30,6 +30,9 @@ import stat from dulwich.errors import ( ObjectFormatException, ) +from dulwich.misc import ( + TreeEntry, + ) from dulwich.objects import ( Blob, Tree, @@ -425,9 +428,9 @@ _TREE_ITEMS = { } _SORTED_TREE_ITEMS = [ - ('a.c', 0100755, 'd80c186a03f423a81b39df39dc87fd269736ca86'), - ('a', stat.S_IFDIR, 'd80c186a03f423a81b39df39dc87fd269736ca86'), - ('a/c', stat.S_IFDIR, 'd80c186a03f423a81b39df39dc87fd269736ca86'), + TreeEntry('a.c', 0100755, 'd80c186a03f423a81b39df39dc87fd269736ca86'), + TreeEntry('a', stat.S_IFDIR, 'd80c186a03f423a81b39df39dc87fd269736ca86'), + TreeEntry('a/c', stat.S_IFDIR, 'd80c186a03f423a81b39df39dc87fd269736ca86'), ] @@ -477,7 +480,9 @@ class TreeTests(ShaFileCheckTests): def do_sort(entries): return list(sorted_tree_items(entries)) - self.assertEqual(_SORTED_TREE_ITEMS, do_sort(_TREE_ITEMS)) + actual = do_sort(_TREE_ITEMS) + self.assertEqual(_SORTED_TREE_ITEMS, actual) + self.assertTrue(isinstance(actual[0], TreeEntry)) # C/Python implementations may differ in specific error types, but # should all error on invalid inputs. -- 1.7.2 _______________________________________________ Mailing list: https://launchpad.net/~dulwich-users Post to : [email protected] Unsubscribe : https://launchpad.net/~dulwich-users More help : https://help.launchpad.net/ListHelp

