From 296f521496858b3790e0aee58a9a0d975122c26b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Iba=C3=B1ez?= Date: Fri, 11 Feb 2011 18:03:33 +0100 Subject: [PATCH] Start support for revision walking --- README.md | 2 + pygit2.c | 140 +++++++++++++++++++++++++++++++++++++++++++ test/__init__.py | 2 +- test/test_revwalk.py | 41 +++++++++++++ 4 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 test/test_revwalk.py diff --git a/README.md b/README.md index 543b89f..1a2f6a8 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,8 @@ AUTHORS ============== * David Borowitz +* J. David Ibáñez + LICENSE ============== diff --git a/pygit2.c b/pygit2.c index fa1a17e..cf05bbb 100644 --- a/pygit2.c +++ b/pygit2.c @@ -70,6 +70,12 @@ typedef struct { git_index_entry *entry; } IndexEntry; +typedef struct { + PyObject_HEAD + Repository *repo; + git_revwalk *walk; +} RevWalker; + static PyTypeObject RepositoryType; static PyTypeObject ObjectType; static PyTypeObject CommitType; @@ -79,6 +85,7 @@ static PyTypeObject BlobType; static PyTypeObject TagType; static PyTypeObject IndexType; static PyTypeObject IndexEntryType; +static PyTypeObject RevWalkerType; static PyObject *GitError; @@ -93,6 +100,8 @@ Error_type(int err) { return PyExc_ValueError; case GIT_ENOMEM: return PyExc_MemoryError; + case GIT_EREVWALKOVER: + return PyExc_StopIteration; default: return GitError; } @@ -327,7 +336,59 @@ Repository_get_index(Repository *self, void *closure) { return self->index; } +static PyObject * +Repository_log(Repository *self, PyObject *py_hex) +{ + int err; + char *hex; + git_oid oid; + git_commit *commit; + git_revwalk *walk; + RevWalker *py_walker; + + hex = PyString_AsString(py_hex); + if (!hex) + return NULL; + + err = git_oid_mkstr(&oid, hex); + if (err < 0) + return Error_set_str(err, hex); + + err = git_commit_lookup(&commit, self->repo, &oid); + if (err < 0) + return Error_set_str(err, hex); + + err = git_revwalk_new(&walk, self->repo); + if (err < 0) + return Error_set(err); + + err = git_revwalk_sorting(walk, GIT_SORT_TOPOLOGICAL | GIT_SORT_REVERSE); + if (err < 0) { + git_revwalk_free(walk); + return Error_set(err); + } + + err = git_revwalk_push(walk, commit); + if (err < 0) { + git_revwalk_free(walk); + return Error_set(err); + } + + py_walker = PyObject_New(RevWalker, &RevWalkerType); + if (!py_walker) { + git_revwalk_free(walk); + return NULL; + } + + Py_INCREF(self); + py_walker->repo = self; + py_walker->walk = walk; + return (PyObject*)py_walker; +} + static PyMethodDef Repository_methods[] = { + {"log", (PyCFunction)Repository_log, METH_O, + "Generator that traverses the history starting from the given commit."}, {"read", (PyCFunction)Repository_read, METH_O, "Read raw object data from the repository."}, {NULL, NULL, 0, NULL} @@ -1621,6 +1682,82 @@ static PyTypeObject IndexEntryType = { 0, /* tp_new */ }; +static void +RevWalker_dealloc(RevWalker *self) { + git_revwalk_free(self->walk); + Py_DECREF(self->repo); + self->ob_type->tp_free((PyObject*)self); +} + +static PyObject * +RevWalker_iter(RevWalker *self) { + Py_INCREF(self); + return (PyObject*)self; +} + +static PyObject * +RevWalker_iternext(RevWalker *self) { + int err; + git_commit *commit; + Commit *py_commit; + + err = git_revwalk_next(&commit, self->walk); + if (err < 0) + return Error_set(err); + + py_commit = PyObject_New(Commit, &CommitType); + if (!py_commit) + return NULL; + py_commit->commit = commit; + Py_INCREF(self->repo); + py_commit->repo = self->repo; + py_commit->own_obj = 0; + + return (PyObject*)py_commit; +} + +static PyTypeObject RevWalkerType = { + PyObject_HEAD_INIT(NULL) + 0, /* ob_size */ + "pygit2.RevWalker", /* tp_name */ + sizeof(RevWalker), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)RevWalker_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 | Py_TPFLAGS_HAVE_ITER, /* tp_flags */ + "Revision walker", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + (getiterfunc)RevWalker_iter, /* tp_iter */ + (iternextfunc)RevWalker_iternext, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ +}; + static PyMethodDef module_methods[] = { {NULL} }; @@ -1664,6 +1801,9 @@ initpygit2(void) IndexEntryType.tp_new = PyType_GenericNew; if (PyType_Ready(&IndexEntryType) < 0) return; + RevWalkerType.tp_new = PyType_GenericNew; + if (PyType_Ready(&RevWalkerType) < 0) + return; m = Py_InitModule3("pygit2", module_methods, "Python bindings for libgit2."); diff --git a/test/__init__.py b/test/__init__.py index 2dc2c1e..93e908f 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -35,8 +35,8 @@ import sys import unittest +names = ['blob', 'commit', 'index', 'repository', 'revwalk', 'tag', 'tree'] def test_suite(): - names = ['blob', 'commit', 'index', 'repository', 'tag', 'tree'] modules = ['test.test_%s' % n for n in names] return unittest.defaultTestLoader.loadTestsFromNames(modules) diff --git a/test/test_revwalk.py b/test/test_revwalk.py new file mode 100644 index 0000000..b08c1a9 --- /dev/null +++ b/test/test_revwalk.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +# +# Copyright 2011 Itaapy +# +# This file is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2, +# as published by the Free Software Foundation. +# +# In addition to the permissions in the GNU General Public License, +# the authors give you unlimited permission to link the compiled +# version of this file into combinations with other programs, +# and to distribute those combinations without any restriction +# coming from the use of this file. (The General Public License +# restrictions do apply in other respects; for example, they cover +# modification of the file, and distribution when not linked into +# a combined executable.) +# +# This file is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; see the file COPYING. If not, write to +# the Free Software Foundation, 51 Franklin Street, Fifth Floor, +# Boston, MA 02110-1301, USA. + +"""Tests for revision walk.""" + +__author__ = 'jdavid@itaapy.com (J. David Ibáñez)' + +import utils + + +class WalkerTest(utils.BareRepoTestCase): + + def test_walk(self): + history = self.repo.log("5fe808e8953c12735680c257f56600cb0de44b10") + history = list(history) + self.assertEqual(len(history), 2)