/*
 * Copyright 2010 Google, Inc.
 * 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.
 */

#include <Python.h>
#include <git2.h>


typedef struct {
    PyObject_HEAD
    git_repository *repo;
    PyObject *index; /* It will be None for a bare repository */
} Repository;

/* The structs for some of the object subtypes are identical except for the
 * type of their object pointers. */
#define OBJECT_STRUCT(_name, _ptr_type, _ptr_name) \
        typedef struct {\
            PyObject_HEAD\
            Repository *repo;\
            _ptr_type *_ptr_name;\
        } _name;

OBJECT_STRUCT(Object, git_object, obj)
OBJECT_STRUCT(Commit, git_commit, commit)
OBJECT_STRUCT(Tree, git_tree, tree)
OBJECT_STRUCT(Blob, git_blob, blob)

typedef struct {
    PyObject_HEAD
    Repository *repo;
    git_tag *tag;
    PyObject *target;
} Tag;

typedef struct {
    PyObject_HEAD
    const git_tree_entry *entry;
    Tree *tree;
} TreeEntry;

typedef struct {
    PyObject_HEAD
    Repository *repo;
    git_index *index;
    int own_obj:1;
} Index;

typedef struct {
    PyObject_HEAD
    git_index_entry *entry;
} IndexEntry;

typedef struct {
    PyObject_HEAD
    Repository *repo;
    git_revwalk *walk;
} Walker;

static PyTypeObject RepositoryType;
static PyTypeObject ObjectType;
static PyTypeObject CommitType;
static PyTypeObject TreeEntryType;
static PyTypeObject TreeType;
static PyTypeObject BlobType;
static PyTypeObject TagType;
static PyTypeObject IndexType;
static PyTypeObject IndexEntryType;
static PyTypeObject WalkerType;

static PyObject *GitError;

static PyObject *
Error_type(int err) {
    switch (err) {
        case GIT_ENOTFOUND:
            return PyExc_KeyError;
        case GIT_EOSERR:
            return PyExc_OSError;
        case GIT_ENOTOID:
            return PyExc_ValueError;
        case GIT_ENOMEM:
            return PyExc_MemoryError;
        case GIT_EREVWALKOVER:
            return PyExc_StopIteration;
        default:
            return GitError;
    }
}

static PyObject *
Error_set(int err) {
    assert(err < 0);
    if (err == GIT_ENOTFOUND) {
        /* KeyError expects the arg to be the missing key. If the caller called
         * this instead of Error_set_py_obj, it means we don't know the key, but
         * nor should we use git_strerror. */
        PyErr_SetNone(PyExc_KeyError);
        return NULL;
    } else if (err == GIT_EOSERR) {
        PyErr_SetFromErrno(GitError);
        return NULL;
    }
    PyErr_SetString(Error_type(err), git_strerror(err));
    return NULL;
}

static PyObject *
Error_set_str(int err, const char *str) {
    if (err == GIT_ENOTFOUND) {
        /* KeyError expects the arg to be the missing key. */
        PyErr_Format(PyExc_KeyError, "%s", str);
        return NULL;
    }
    PyErr_Format(Error_type(err), "%s: %s", str, git_strerror(err));
    return NULL;
}

static PyObject *
Error_set_py_obj(int err, PyObject *py_obj) {
    PyObject *py_str;
    char *str;

    assert(err < 0);

    if (err == GIT_ENOTOID && !PyString_Check(py_obj)) {
        PyErr_Format(PyExc_TypeError, "Git object id must be str, not %.200s",
                     py_obj->ob_type->tp_name);
        return NULL;
    } else if (err == GIT_ENOTFOUND) {
        /* KeyError expects the arg to be the missing key. */
        PyErr_SetObject(PyExc_KeyError, py_obj);
        return NULL;
    }
    py_str = PyObject_Str(py_obj);
    str = py_str ? PyString_AS_STRING(py_str) : "<error in __str__>";
    PyErr_Format(Error_type(err), "%s: %s", str, git_strerror(err));
    Py_XDECREF(py_str);
    return NULL;
}

static int
py_str_to_git_oid(PyObject *py_str, git_oid *oid) {
    char *hex;
    int err;

    hex = PyString_AsString(py_str);
    if (hex == NULL) {
        Error_set_py_obj(GIT_ENOTOID, py_str);
        return 0;
    }

    err = git_oid_mkstr(oid, hex);
    if (err < 0) {
        Error_set_py_obj(err, py_str);
        return 0;
    }

    return 1;
}

static int
Repository_init(Repository *self, PyObject *args, PyObject *kwds) {
    char *path;
    int err;

    if (kwds) {
        PyErr_SetString(PyExc_TypeError,
                        "Repository takes no keyword arugments");
        return -1;
    }

    if (!PyArg_ParseTuple(args, "s", &path))
        return -1;

    err = git_repository_open(&self->repo, path);
    if (err < 0) {
        Error_set_str(err, path);
        return -1;
    }

    return 0;
}

static void
Repository_dealloc(Repository *self) {
    if (self->repo)
        git_repository_free(self->repo);
    Py_XDECREF(self->index);
    self->ob_type->tp_free((PyObject*)self);
}

static int
Repository_contains(Repository *self, PyObject *value) {
    git_oid oid;

    if (!py_str_to_git_oid(value, &oid))
        return -1;

    return git_odb_exists(git_repository_database(self->repo), &oid);
}

static Object *
wrap_object(git_object *obj, Repository *repo) {
    Object *py_obj = NULL;
    switch (git_object_type(obj)) {
        case GIT_OBJ_COMMIT:
            py_obj = (Object*)CommitType.tp_alloc(&CommitType, 0);
            break;
        case GIT_OBJ_TREE:
            py_obj = (Object*)TreeType.tp_alloc(&TreeType, 0);
            break;
        case GIT_OBJ_BLOB:
            py_obj = (Object*)BlobType.tp_alloc(&BlobType, 0);
            break;
        case GIT_OBJ_TAG:
            py_obj = (Object*)TagType.tp_alloc(&TagType, 0);
            break;
        default:
            assert(0);
    }
    if (!py_obj)
        return (Object*)PyErr_NoMemory();

    py_obj->obj = obj;
    py_obj->repo = repo;
    Py_INCREF(repo);
    return py_obj;
}

static PyObject *
Repository_getitem(Repository *self, PyObject *value) {
    git_oid oid;
    int err;
    git_object *obj;
    Object *py_obj;

    if (!py_str_to_git_oid(value, &oid))
        return NULL;

    err = git_object_lookup(&obj, self->repo, &oid, GIT_OBJ_ANY);
    if (err < 0)
        return Error_set_py_obj(err, value);

    py_obj = wrap_object(obj, self);
    if (!py_obj)
        return NULL;
    return (PyObject*)py_obj;
}

static int
Repository_read_raw(git_odb_object **obj, git_repository *repo, const git_oid *oid) {
    return git_odb_read(obj, git_repository_database(repo), oid);
}

static PyObject *
Repository_read(Repository *self, PyObject *py_hex) {
    git_oid oid;
    int err;
    git_odb_object *obj;

    if (!py_str_to_git_oid(py_hex, &oid))
        return NULL;

    err = Repository_read_raw(&obj, self->repo, &oid);
    if (err < 0)
        return Error_set_py_obj(err, py_hex);

    return Py_BuildValue(
        "(ns#)",
        git_odb_object_type(obj),
        git_odb_object_data(obj),
        git_odb_object_size(obj));
}

static PyObject *
Repository_get_index(Repository *self, void *closure) {
    int err;
    git_index *index;
    Index *py_index;

    assert(self->repo);

    if (self->index == NULL) {
        err = git_repository_index(&index, self->repo);
        if (err == GIT_SUCCESS) {
            py_index = (Index*)IndexType.tp_alloc(&IndexType, 0);
            if (!py_index)
                return PyErr_NoMemory();
            py_index->repo = self;
            py_index->index = index;
            py_index->own_obj = 0;
            self->index = (PyObject*)py_index;
        } else if (err == GIT_EBAREINDEX) {
            Py_INCREF(Py_None);
            self->index = Py_None;
        } else {
            return Error_set(err);
        }
    }

    Py_INCREF(self->index);
    return self->index;
}

static PyObject *
Repository_walk(Repository *self, PyObject *args)
{
    PyObject *value;
    unsigned int sort;
    int err;
    git_oid oid;
    git_revwalk *walk;
    Walker *py_walker;

    if (!PyArg_ParseTuple(args, "OI", &value, &sort))
        return NULL;

    if (value != Py_None && !PyString_Check(value)) {
        PyErr_SetObject(PyExc_TypeError, value);
        return NULL;
    }

    err = git_revwalk_new(&walk, self->repo);
    if (err < 0)
        return Error_set(err);

    /* Sort */
    git_revwalk_sorting(walk, sort);

    /* Push */
    if (value != Py_None) {
        if (!py_str_to_git_oid(value, &oid)) {
            git_revwalk_free(walk);
            return NULL;
        }
        err = git_revwalk_push(walk, &oid);
        if (err < 0) {
            git_revwalk_free(walk);
            return Error_set(err);
        }
    }

    py_walker = PyObject_New(Walker, &WalkerType);
    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 PyObject *
build_person(const git_signature *signature) {
    return Py_BuildValue("(ssLi)", signature->name, signature->email,
                         signature->when.time, signature->when.offset);
}

static int
signature_converter(PyObject *value, git_signature **out) {
    char *name, *email;
    long long time;
    int offset;
    git_signature *signature;

    if (!PyArg_ParseTuple(value, "ssLi", &name, &email, &time, &offset))
        return 0;

    signature = git_signature_new(name, email, time, offset);
    if (signature == NULL) {
        PyErr_SetNone(PyExc_MemoryError);
        return 0;
    }

    *out = signature;
    return 1;
}

static PyObject *
free_parents(git_oid **parents, int n) {
    int i;

    for (i = 0; i < n; i++)
        free(parents[i]);
    free(parents);
    return NULL;
}

static PyObject *
Repository_create_commit(Repository *self, PyObject *args) {
    git_signature *author, *committer;
    char *message, *update_ref;
    git_oid tree_oid, oid;
    PyObject *py_parents, *py_parent;
    int parent_count;
    git_oid **parents;
    int err, i;
    char hex[GIT_OID_HEXSZ];

    if (!PyArg_ParseTuple(args, "zO&O&sO&O!",
                          &update_ref,
                          signature_converter, &author,
                          signature_converter, &committer,
                          &message,
                          py_str_to_git_oid, &tree_oid,
                          &PyList_Type, &py_parents))
        return NULL;

    parent_count = (int)PyList_Size(py_parents);
    parents = malloc(parent_count * sizeof(git_oid*));
    if (parents == NULL) {
        PyErr_SetNone(PyExc_MemoryError);
        return NULL;
    }
    for (i = 0; i < parent_count; i++) {
        parents[i] = malloc(sizeof(git_oid));
        if (parents[i] == NULL) {
            PyErr_SetNone(PyExc_MemoryError);
            return free_parents(parents, i);
        }
        py_parent = PyList_GET_ITEM(py_parents, i);
        if (!py_str_to_git_oid(py_parent, parents[i]))
            return free_parents(parents, i);
    }

    err = git_commit_create(&oid, self->repo, update_ref, author, committer,
        message, &tree_oid, parent_count, (const git_oid**)parents);
    free_parents(parents, parent_count);
    if (err < 0)
        return Error_set(err);

    git_oid_fmt(hex, &oid);
    return PyString_FromStringAndSize(hex, GIT_OID_HEXSZ);
}

static PyObject *
Repository_create_tag(Repository *self, PyObject *args) {
    char *tag_name, *message;
    git_signature *tagger;
    git_oid target, oid;
    int err, target_type;
    char hex[GIT_OID_HEXSZ];

    if (!PyArg_ParseTuple(args, "sO&iO&s",
                          &tag_name,
                          py_str_to_git_oid, &target,
                          &target_type,
                          signature_converter, &tagger,
                          &message))
        return NULL;

    err = git_tag_create(&oid, self->repo,
        tag_name, &target, target_type, tagger, message);
    if (err < 0)
        return NULL;

    git_oid_fmt(hex, &oid);
    return PyString_FromStringAndSize(hex, GIT_OID_HEXSZ);
}

static PyMethodDef Repository_methods[] = {
    {"create_commit", (PyCFunction)Repository_create_commit, METH_VARARGS,
     "Create a new commit object, return its SHA."},
    {"create_tag", (PyCFunction)Repository_create_tag, METH_VARARGS,
     "Create a new tag object, return its SHA."},
    {"walk", (PyCFunction)Repository_walk, METH_VARARGS,
     "Generator that traverses the history starting from the given commit."},
    {"read", (PyCFunction)Repository_read, METH_O,
     "Read raw object data from the repository."},
    {NULL}
};

static PyGetSetDef Repository_getseters[] = {
    {"index", (getter)Repository_get_index, NULL, "index file. ", NULL},
    {NULL}
};

static PySequenceMethods Repository_as_sequence = {
    0,                               /* sq_length */
    0,                               /* sq_concat */
    0,                               /* sq_repeat */
    0,                               /* sq_item */
    0,                               /* sq_slice */
    0,                               /* sq_ass_item */
    0,                               /* sq_ass_slice */
    (objobjproc)Repository_contains, /* sq_contains */
};

static PyMappingMethods Repository_as_mapping = {
    0,                               /* mp_length */
    (binaryfunc)Repository_getitem,  /* mp_subscript */
    0,                               /* mp_ass_subscript */
};

static PyTypeObject RepositoryType = {
    PyObject_HEAD_INIT(NULL)
    0,                                         /* ob_size */
    "pygit2.Repository",                       /* tp_name */
    sizeof(Repository),                        /* tp_basicsize */
    0,                                         /* tp_itemsize */
    (destructor)Repository_dealloc,            /* tp_dealloc */
    0,                                         /* tp_print */
    0,                                         /* tp_getattr */
    0,                                         /* tp_setattr */
    0,                                         /* tp_compare */
    0,                                         /* tp_repr */
    0,                                         /* tp_as_number */
    &Repository_as_sequence,                   /* tp_as_sequence */
    &Repository_as_mapping,                    /* 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_BASETYPE,  /* tp_flags */
    "Git repository",                          /* tp_doc */
    0,                                         /* tp_traverse */
    0,                                         /* tp_clear */
    0,                                         /* tp_richcompare */
    0,                                         /* tp_weaklistoffset */
    0,                                         /* tp_iter */
    0,                                         /* tp_iternext */
    Repository_methods,                        /* tp_methods */
    0,                                         /* tp_members */
    Repository_getseters,                      /* tp_getset */
    0,                                         /* tp_base */
    0,                                         /* tp_dict */
    0,                                         /* tp_descr_get */
    0,                                         /* tp_descr_set */
    0,                                         /* tp_dictoffset */
    (initproc)Repository_init,                 /* tp_init */
    0,                                         /* tp_alloc */
    0,                                         /* tp_new */
};

static void
Object_dealloc(Object* self)
{
    git_object_close(self->obj);
    Py_XDECREF(self->repo);
    self->ob_type->tp_free((PyObject*)self);
}

static PyObject *
Object_get_type(Object *self) {
    return PyInt_FromLong(git_object_type(self->obj));
}

static PyObject *
Object_get_sha(Object *self) {
    const git_oid *id;
    char hex[GIT_OID_HEXSZ];

    id = git_object_id(self->obj);
    if (!id)
        Py_RETURN_NONE;

    git_oid_fmt(hex, id);
    return PyString_FromStringAndSize(hex, GIT_OID_HEXSZ);
}

static PyObject *
Object_read_raw(Object *self) {
    const git_oid *id;
    git_odb_object *obj;
    int err;
    PyObject *result = NULL, *py_sha = NULL;

    id = git_object_id(self->obj);
    if (!id)
        Py_RETURN_NONE;  /* in-memory object */

    err = Repository_read_raw(&obj, self->repo->repo, id);
    if (err < 0) {
        py_sha = Object_get_sha(self);
        Error_set_py_obj(err, py_sha);
        goto cleanup;
    }

    result = PyString_FromStringAndSize(
        git_odb_object_data(obj),
        git_odb_object_size(obj));

cleanup:
    Py_XDECREF(py_sha);
    return result;
}

static PyGetSetDef Object_getseters[] = {
    {"type", (getter)Object_get_type, NULL, "type number", NULL},
    {"sha", (getter)Object_get_sha, NULL, "hex SHA", NULL},
    {NULL}
};

static PyMethodDef Object_methods[] = {
    {"read_raw", (PyCFunction)Object_read_raw, METH_NOARGS,
     "Read the raw contents of the object from the repo."},
    {NULL}
};

static PyTypeObject ObjectType = {
    PyObject_HEAD_INIT(NULL)
    0,                                         /*ob_size*/
    "pygit2.Object",                           /*tp_name*/
    sizeof(Object),                            /*tp_basicsize*/
    0,                                         /*tp_itemsize*/
    (destructor)Object_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_BASETYPE,  /*tp_flags*/
    "Object objects",                          /* tp_doc */
    0,                                         /* tp_traverse */
    0,                                         /* tp_clear */
    0,                                         /* tp_richcompare */
    0,                                         /* tp_weaklistoffset */
    0,                                         /* tp_iter */
    0,                                         /* tp_iternext */
    Object_methods,                            /* tp_methods */
    0,                                         /* tp_members */
    Object_getseters,                          /* 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 PyObject *
Commit_get_message_short(Commit *commit) {
    return PyString_FromString(git_commit_message_short(commit->commit));
}

static PyObject *
Commit_get_message(Commit *commit) {
    return PyString_FromString(git_commit_message(commit->commit));
}

static PyObject *
Commit_get_commit_time(Commit *commit) {
    return PyLong_FromLong(git_commit_time(commit->commit));
}

static PyObject *
Commit_get_committer(Commit *commit) {
    const git_signature *signature = git_commit_committer(commit->commit);

    return build_person(signature);
}

static PyObject *
Commit_get_author(Commit *commit) {
    const git_signature *signature = git_commit_author(commit->commit);

    return build_person(signature);
}

static PyObject *
Commit_get_tree(Commit *commit) {
    git_tree *tree;
    Tree *py_tree;
    int err;

    err = git_commit_tree(&tree, commit->commit);
    if (err == GIT_ENOTFOUND)
        Py_RETURN_NONE;

    if (err < 0)
        return Error_set(err);

    py_tree = PyObject_New(Tree, &TreeType);
    Py_INCREF(commit->repo);
    py_tree->repo = commit->repo;
    py_tree->tree = (git_tree*)tree;

    return (PyObject*)py_tree;
}

static PyObject *
Commit_get_parents(Commit *commit)
{
    unsigned int i, parent_count;
    int err;
    git_commit *parent;
    PyObject *obj;
    PyObject *list;

    parent_count = git_commit_parentcount(commit->commit);
    list = PyList_New(parent_count);
    if (!list)
        return NULL;

    for (i=0; i < parent_count; i++) {
        err = git_commit_parent(&parent, commit->commit, i);
        if (err < 0) {
            Py_DECREF(list);
            Error_set(err);
            return NULL;
        }
        obj = (PyObject*)wrap_object((git_object *)parent, commit->repo);
        if (obj == NULL) {
            Py_DECREF(list);
            return NULL;
        }

        PyList_SET_ITEM(list, i, obj);
    }

    return list;
}

static PyGetSetDef Commit_getseters[] = {
    {"message_short", (getter)Commit_get_message_short, NULL, "short message",
     NULL},
    {"message", (getter)Commit_get_message, NULL, "message", NULL},
    {"commit_time", (getter)Commit_get_commit_time, NULL, "commit time",
     NULL},
    {"committer", (getter)Commit_get_committer, NULL, "committer", NULL},
    {"author", (getter)Commit_get_author, NULL, "author", NULL},
    {"tree", (getter)Commit_get_tree, NULL, "tree object", NULL},
    {"parents", (getter)Commit_get_parents, NULL, "parents of this commit",
      NULL},
    {NULL}
};

static PyTypeObject CommitType = {
    PyObject_HEAD_INIT(NULL)
    0,                                         /*ob_size*/
    "pygit2.Commit",                           /*tp_name*/
    sizeof(Commit),                            /*tp_basicsize*/
    0,                                         /*tp_itemsize*/
    0,                                         /*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_BASETYPE,  /*tp_flags*/
    "Commit objects",                          /* 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 */
    Commit_getseters,                          /* 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 void
TreeEntry_dealloc(TreeEntry *self) {
    Py_XDECREF(self->tree);
    self->ob_type->tp_free((PyObject *)self);
}

static PyObject *
TreeEntry_get_attributes(TreeEntry *self) {
    return PyInt_FromLong(git_tree_entry_attributes(self->entry));
}

static PyObject *
TreeEntry_get_name(TreeEntry *self) {
    return PyString_FromString(git_tree_entry_name(self->entry));
}

static PyObject *
TreeEntry_get_sha(TreeEntry *self) {
    char hex[GIT_OID_HEXSZ];
    git_oid_fmt(hex, git_tree_entry_id(self->entry));
    return PyString_FromStringAndSize(hex, GIT_OID_HEXSZ);
}

static PyObject *
TreeEntry_to_object(TreeEntry *self) {
    git_object *obj;
    int err;
    char hex[GIT_OID_HEXSZ + 1];

    err = git_tree_entry_2object(&obj, self->tree->repo->repo, self->entry);
    if (err < 0) {
        git_oid_fmt(hex, git_tree_entry_id(self->entry));
        hex[GIT_OID_HEXSZ] = '\0';
        return Error_set_str(err, hex);
    }
    return (PyObject*)wrap_object(obj, self->tree->repo);
}

static PyGetSetDef TreeEntry_getseters[] = {
    {"attributes", (getter)TreeEntry_get_attributes, NULL, "attributes", NULL},
    {"name", (getter)TreeEntry_get_name, NULL, "name", NULL},
    {"sha", (getter)TreeEntry_get_sha, NULL, "sha", NULL},
    {NULL}
};

static PyMethodDef TreeEntry_methods[] = {
    {"to_object", (PyCFunction)TreeEntry_to_object, METH_NOARGS,
     "Look up the corresponding object in the repo."},
    {NULL, NULL, 0, NULL}
};

static PyTypeObject TreeEntryType = {
    PyObject_HEAD_INIT(NULL)
    0,                                         /*ob_size*/
    "pygit2.TreeEntry",                        /*tp_name*/
    sizeof(TreeEntry),                         /*tp_basicsize*/
    0,                                         /*tp_itemsize*/
    (destructor)TreeEntry_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_BASETYPE,  /*tp_flags*/
    "TreeEntry objects",                       /* tp_doc */
    0,                                         /* tp_traverse */
    0,                                         /* tp_clear */
    0,                                         /* tp_richcompare */
    0,                                         /* tp_weaklistoffset */
    0,                                         /* tp_iter */
    0,                                         /* tp_iternext */
    TreeEntry_methods,                         /* tp_methods */
    0,                                         /* tp_members */
    TreeEntry_getseters,                       /* 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 Py_ssize_t
Tree_len(Tree *self) {
    assert(self->tree);
    return (Py_ssize_t)git_tree_entrycount(self->tree);
}

static int
Tree_contains(Tree *self, PyObject *py_name) {
    char *name;

    name = PyString_AsString(py_name);
    if (name == NULL)
        return -1;

    return git_tree_entry_byname(self->tree, name) ? 1 : 0;
}

static TreeEntry *
wrap_tree_entry(const git_tree_entry *entry, Tree *tree) {
    TreeEntry *py_entry = NULL;
    py_entry = (TreeEntry*)TreeEntryType.tp_alloc(&TreeEntryType, 0);
    if (!py_entry)
        return NULL;

    py_entry->entry = entry;
    py_entry->tree = tree;
    Py_INCREF(tree);
    return py_entry;
}

static TreeEntry *
Tree_getitem_by_name(Tree *self, PyObject *py_name) {
    char *name;
    const git_tree_entry *entry;

    name = PyString_AS_STRING(py_name);
    entry = git_tree_entry_byname(self->tree, name);
    if (!entry) {
        PyErr_SetObject(PyExc_KeyError, py_name);
        return NULL;
    }
    return wrap_tree_entry(entry, self);
}

static int
Tree_fix_index(Tree *self, PyObject *py_index) {
    long index;
    size_t len;
    long slen;

    index = PyInt_AsLong(py_index);
    if (PyErr_Occurred())
        return -1;

    len = git_tree_entrycount(self->tree);
    slen = (long)len;
    if (index >= slen) {
        PyErr_SetObject(PyExc_IndexError, py_index);
        return -1;
    } else if (index < -slen) {
        PyErr_SetObject(PyExc_IndexError, py_index);
        return -1;
    }

    /* This function is called via mp_subscript, which doesn't do negative index
     * rewriting, so we have to do it manually. */
    if (index < 0)
        index = len + index;
    return (int)index;
}

static TreeEntry *
Tree_getitem_by_index(Tree *self, PyObject *py_index) {
    int index;
    const git_tree_entry *entry;

    index = Tree_fix_index(self, py_index);
    if (PyErr_Occurred())
        return NULL;

    entry = git_tree_entry_byindex(self->tree, index);
    if (!entry) {
        PyErr_SetObject(PyExc_IndexError, py_index);
        return NULL;
    }
    return wrap_tree_entry(entry, self);
}

static TreeEntry *
Tree_getitem(Tree *self, PyObject *value) {
    if (PyString_Check(value)) {
        return Tree_getitem_by_name(self, value);
    } else if (PyInt_Check(value)) {
        return Tree_getitem_by_index(self, value);
    } else {
        PyErr_Format(PyExc_TypeError,
                     "Tree entry index must be int or str, not %.200s",
                     value->ob_type->tp_name);
        return NULL;
    }
}

static PySequenceMethods Tree_as_sequence = {
    0,                          /* sq_length */
    0,                          /* sq_concat */
    0,                          /* sq_repeat */
    0,                          /* sq_item */
    0,                          /* sq_slice */
    0,                          /* sq_ass_item */
    0,                          /* sq_ass_slice */
    (objobjproc)Tree_contains,  /* sq_contains */
};

static PyMappingMethods Tree_as_mapping = {
    (lenfunc)Tree_len,            /* mp_length */
    (binaryfunc)Tree_getitem,     /* mp_subscript */
    0,                            /* mp_ass_subscript */
};

static PyTypeObject TreeType = {
    PyObject_HEAD_INIT(NULL)
    0,                                         /*ob_size*/
    "pygit2.Tree",                             /*tp_name*/
    sizeof(Tree),                              /*tp_basicsize*/
    0,                                         /*tp_itemsize*/
    0,                                         /*tp_dealloc*/
    0,                                         /*tp_print*/
    0,                                         /*tp_getattr*/
    0,                                         /*tp_setattr*/
    0,                                         /*tp_compare*/
    0,                                         /*tp_repr*/
    0,                                         /*tp_as_number*/
    &Tree_as_sequence,                         /*tp_as_sequence*/
    &Tree_as_mapping,                          /*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_BASETYPE,  /*tp_flags*/
    "Tree objects",                            /* 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 */
    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 PyGetSetDef Blob_getseters[] = {
    {"data", (getter)Object_read_raw, NULL, "raw data", NULL},
    {NULL}
};

static PyTypeObject BlobType = {
    PyObject_HEAD_INIT(NULL)
    0,                                         /*ob_size*/
    "pygit2.Blob",                             /*tp_name*/
    sizeof(Blob),                              /*tp_basicsize*/
    0,                                         /*tp_itemsize*/
    0,                                         /*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_BASETYPE,  /*tp_flags*/
    "Blob objects",                            /* 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 */
    Blob_getseters,                            /* 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 void
Tag_dealloc(Tag *self) {
    Py_XDECREF(self->target);
    self->ob_type->tp_free((PyObject*)self);
}

static PyObject *
Tag_get_target(Tag *self) {
    git_object *target;
    int err;

    if (self->target == NULL) {
        err = git_tag_target(&target, self->tag);
        if (err == GIT_ENOTFOUND) {
            /* This can only happen if we have a new tag with no target set
             * yet. */
            Py_INCREF(Py_None);
            self->target = Py_None;
        } else if (err < 0)
            return Error_set(err);
        else
            self->target = (PyObject*)wrap_object(target, self->repo);
    }

    Py_INCREF(self->target);
    return self->target;
}

static PyObject *
Tag_get_name(Tag *self) {
    const char *name;
    name = git_tag_name(self->tag);
    if (!name)
        Py_RETURN_NONE;
    return PyString_FromString(name);
}

static PyObject *
Tag_get_tagger(Tag *tag) {
    const git_signature *signature = git_tag_tagger(tag->tag);
    if (!signature)
        Py_RETURN_NONE;
    return build_person(signature);
}

static PyObject *
Tag_get_message(Tag *self) {
    const char *message;
    message = git_tag_message(self->tag);
    if (!message)
        Py_RETURN_NONE;
    return PyString_FromString(message);
}

static PyGetSetDef Tag_getseters[] = {
    {"target", (getter)Tag_get_target, NULL, "tagged object", NULL},
    {"name", (getter)Tag_get_name, NULL, "tag name", NULL},
    {"tagger", (getter)Tag_get_tagger, NULL, "tagger", NULL},
    {"message", (getter)Tag_get_message, NULL, "tag message",
     NULL},
    {NULL}
};

static PyTypeObject TagType = {
    PyObject_HEAD_INIT(NULL)
    0,                                         /*ob_size*/
    "pygit2.Tag",                              /*tp_name*/
    sizeof(Tag),                               /*tp_basicsize*/
    0,                                         /*tp_itemsize*/
    (destructor)Tag_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_BASETYPE,  /*tp_flags*/
    "Tag objects",                             /* 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 */
    Tag_getseters,                             /* 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 int
Index_init(Index *self, PyObject *args, PyObject *kwds) {
    char *path;
    int err;

    if (kwds) {
        PyErr_SetString(PyExc_TypeError,
                        "Index takes no keyword arugments");
        return -1;
    }

    if (!PyArg_ParseTuple(args, "s", &path))
        return -1;

    err = git_index_open_bare(&self->index, path);
    if (err < 0) {
        Error_set_str(err, path);
        return -1;
    }

    self->own_obj = 1;
    return 0;
}

static void
Index_dealloc(Index* self)
{
    if (self->own_obj)
        git_index_free(self->index);
    Py_XDECREF(self->repo);
    self->ob_type->tp_free((PyObject*)self);
}

static PyObject *
Index_add(Index *self, PyObject *args) {
    int err;
    const char *path;
    int stage=0;

    if (!PyArg_ParseTuple(args, "s|i", &path, &stage))
        return NULL;

    err = git_index_add(self->index, path, stage);
    if (err < 0)
        return Error_set_str(err, path);

    Py_RETURN_NONE;
}

static PyObject *
Index_clear(Index *self) {
    git_index_clear(self->index);
    Py_RETURN_NONE;
}

static PyObject *
Index_find(Index *self, PyObject *py_path) {
    char *path;
    long idx;

    path = PyString_AsString(py_path);
    if (!path)
        return NULL;

    idx = (long)git_index_find(self->index, path);
    if (idx < 0)
        return Error_set_str(idx, path);

    return PyInt_FromLong(idx);
}

static PyObject *
Index_read(Index *self) {
    int err;

    err = git_index_read(self->index);
    if (err < GIT_SUCCESS)
        return Error_set(err);

    Py_RETURN_NONE;
}

static PyObject *
Index_write(Index *self) {
    int err;

    err = git_index_write(self->index);
    if (err < GIT_SUCCESS)
        return Error_set(err);

    Py_RETURN_NONE;
}

/* This is an internal function, used by Index_getitem and Index_setitem */
static int
Index_get_position(Index *self, PyObject *value) {
    char *path;
    int idx;

    if (PyString_Check(value)) {
        path = PyString_AsString(value);
        if (!path)
            return -1;
        idx = git_index_find(self->index, path);
        if (idx < 0) {
            Error_set_str(idx, path);
            return -1;
        }
    } else if (PyInt_Check(value)) {
        idx = (int)PyInt_AsLong(value);
        if (idx == -1 && PyErr_Occurred())
            return -1;
        if (idx < 0) {
            PyErr_SetObject(PyExc_ValueError, value);
            return -1;
        }
    } else {
        PyErr_Format(PyExc_TypeError,
                     "Index entry key must be int or str, not %.200s",
                     value->ob_type->tp_name);
        return -1;
    }

    return idx;
}

static int
Index_contains(Index *self, PyObject *value) {
    char *path;
    int idx;

    path = PyString_AsString(value);
    if (!path)
        return -1;
    idx = git_index_find(self->index, path);
    if (idx == GIT_ENOTFOUND)
        return 0;
    if (idx < 0) {
        Error_set_str(idx, path);
        return -1;
    }

    return 1;
}

static Py_ssize_t
Index_len(Index *self) {
    return (Py_ssize_t)git_index_entrycount(self->index);
}

static PyObject *
Index_getitem(Index *self, PyObject *value) {
    int idx;
    git_index_entry *index_entry;
    IndexEntry *py_index_entry;

    idx = Index_get_position(self, value);
    if (idx == -1)
        return NULL;

    index_entry = git_index_get(self->index, idx);
    if (!index_entry) {
        PyErr_SetObject(PyExc_KeyError, value);
        return NULL;
    }

    py_index_entry = (IndexEntry*)IndexEntryType.tp_alloc(&IndexEntryType, 0);
    if (!py_index_entry)
        return PyErr_NoMemory();

    py_index_entry->entry = index_entry;

    Py_INCREF(py_index_entry);
    return (PyObject*)py_index_entry;
}

static int
Index_setitem(Index *self, PyObject *key, PyObject *value) {
    int err;
    int idx;

    if (value) {
        PyErr_SetString(PyExc_NotImplementedError,
                        "set item on index not yet implemented");
        return -1;
    }

    idx = Index_get_position(self, key);
    if (idx == -1)
        return -1;

    err = git_index_remove(self->index, idx);
    if (err < 0) {
        Error_set(err);
        return -1;
    }

    return 0;
}

static PyObject *
Index_create_tree(Index *self) {
    git_oid oid;
    int err;
    char hex[GIT_OID_HEXSZ];

    err = git_tree_create_fromindex(&oid, self->index);
    if (err < 0)
        return Error_set(err);

    git_oid_fmt(hex, &oid);
    return PyString_FromStringAndSize(hex, GIT_OID_HEXSZ);
}

static PyMethodDef Index_methods[] = {
    {"add", (PyCFunction)Index_add, METH_VARARGS,
     "Add or update an index entry from a file in disk."},
    {"clear", (PyCFunction)Index_clear, METH_NOARGS,
     "Clear the contents (all the entries) of an index object."},
    {"_find", (PyCFunction)Index_find, METH_O,
     "Find the first index of any entries which point to given path in the"
     " Git index."},
    {"read", (PyCFunction)Index_read, METH_NOARGS,
     "Update the contents of an existing index object in memory by reading"
     " from the hard disk."},
    {"write", (PyCFunction)Index_write, METH_NOARGS,
     "Write an existing index object from memory back to disk using an"
     " atomic file lock."},
    {"create_tree", (PyCFunction)Index_create_tree, METH_NOARGS,
     "Create a tree from the index file, return its SHA."},
    {NULL}
};

static PySequenceMethods Index_as_sequence = {
    0,                          /* sq_length */
    0,                          /* sq_concat */
    0,                          /* sq_repeat */
    0,                          /* sq_item */
    0,                          /* sq_slice */
    0,                          /* sq_ass_item */
    0,                          /* sq_ass_slice */
    (objobjproc)Index_contains, /* sq_contains */
};

static PyMappingMethods Index_as_mapping = {
    (lenfunc)Index_len,              /* mp_length */
    (binaryfunc)Index_getitem,       /* mp_subscript */
    (objobjargproc)Index_setitem,    /* mp_ass_subscript */
};

static PyTypeObject IndexType = {
    PyObject_HEAD_INIT(NULL)
    0,                                         /* ob_size */
    "pygit2.Index",                            /* tp_name */
    sizeof(Index),                             /* tp_basicsize */
    0,                                         /* tp_itemsize */
    (destructor)Index_dealloc,                 /* tp_dealloc */
    0,                                         /* tp_print */
    0,                                         /* tp_getattr */
    0,                                         /* tp_setattr */
    0,                                         /* tp_compare */
    0,                                         /* tp_repr */
    0,                                         /* tp_as_number */
    &Index_as_sequence,                        /* tp_as_sequence */
    &Index_as_mapping,                         /* 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_BASETYPE,  /* tp_flags */
    "Index file",                              /* tp_doc */
    0,                                         /* tp_traverse */
    0,                                         /* tp_clear */
    0,                                         /* tp_richcompare */
    0,                                         /* tp_weaklistoffset */
    0,                                         /* tp_iter */
    0,                                         /* tp_iternext */
    Index_methods,                             /* 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 */
    (initproc)Index_init,                      /* tp_init */
    0,                                         /* tp_alloc */
    0,                                         /* tp_new */
};

static void
IndexEntry_dealloc(IndexEntry *self) {
    self->ob_type->tp_free((PyObject*)self);
}

static PyObject *
IndexEntry_get_path(IndexEntry *self) {
    return PyString_FromString(self->entry->path);
}

static PyObject *
IndexEntry_get_sha(IndexEntry *self) {
    char hex[GIT_OID_HEXSZ];

    git_oid_fmt(hex, &self->entry->oid);
    return PyString_FromStringAndSize(hex, GIT_OID_HEXSZ);
}

static PyGetSetDef IndexEntry_getseters[] = {
    {"path", (getter)IndexEntry_get_path, NULL, "path", NULL},
    {"sha", (getter)IndexEntry_get_sha, NULL, "hex SHA",  NULL},
    {NULL},
};

static PyTypeObject IndexEntryType = {
    PyObject_HEAD_INIT(NULL)
    0,                                         /* ob_size */
    "pygit2.IndexEntry",                       /* tp_name */
    sizeof(IndexEntry),                        /* tp_basicsize */
    0,                                         /* tp_itemsize */
    (destructor)IndexEntry_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 */
    "Index entry",                             /* 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 */
    IndexEntry_getseters,                      /* 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 void
Walker_dealloc(Walker *self) {
    git_revwalk_free(self->walk);
    Py_DECREF(self->repo);
    self->ob_type->tp_free((PyObject*)self);
}

static PyObject *
Walker_hide(Walker *self, PyObject *py_hex) {
    int err;
    git_oid oid;

    if (!py_str_to_git_oid(py_hex, &oid))
        return NULL;

    err = git_revwalk_hide(self->walk, &oid);
    if (err < 0)
        return Error_set(err);

    Py_RETURN_NONE;
}

static PyObject *
Walker_push(Walker *self, PyObject *py_hex) {
    int err;
    git_oid oid;

    if (!py_str_to_git_oid(py_hex, &oid))
        return NULL;

    err = git_revwalk_push(self->walk, &oid);
    if (err < 0)
        return Error_set(err);

    Py_RETURN_NONE;
}

static PyObject *
Walker_sort(Walker *self, PyObject *py_sort_mode) {
    int sort_mode;

    sort_mode = (int)PyInt_AsLong(py_sort_mode);
    if (sort_mode == -1 && PyErr_Occurred())
        return NULL;

    git_revwalk_sorting(self->walk, sort_mode);

    Py_RETURN_NONE;
}

static PyObject *
Walker_reset(Walker *self) {
    git_revwalk_reset(self->walk);
    Py_RETURN_NONE;
}

static PyObject *
Walker_iter(Walker *self) {
    Py_INCREF(self);
    return (PyObject*)self;
}

static PyObject *
Walker_iternext(Walker *self) {
    int err;
    git_commit *commit;
    Commit *py_commit;
    git_oid oid;

    err = git_revwalk_next(&oid, self->walk);
    if (err < 0)
        return Error_set(err);

    err = git_commit_lookup(&commit, self->repo->repo, &oid);
    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;

    return (PyObject*)py_commit;
}

static PyMethodDef Walker_methods[] = {
    {"hide", (PyCFunction)Walker_hide, METH_O,
     "Mark a commit (and its ancestors) uninteresting for the output."},
    {"push", (PyCFunction)Walker_push, METH_O,
     "Mark a commit to start traversal from."},
    {"reset", (PyCFunction)Walker_reset, METH_NOARGS,
     "Reset the walking machinery for reuse."},
    {"sort", (PyCFunction)Walker_sort, METH_O,
     "Change the sorting mode (this resets the walker)."},
    {NULL}
};

static PyTypeObject WalkerType = {
    PyObject_HEAD_INIT(NULL)
    0,                                         /* ob_size */
    "pygit2.Walker",                           /* tp_name */
    sizeof(Walker),                            /* tp_basicsize */
    0,                                         /* tp_itemsize */
    (destructor)Walker_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)Walker_iter,                  /* tp_iter */
    (iternextfunc)Walker_iternext,             /* tp_iternext */
    Walker_methods,                            /* 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 PyObject *
init_repository(PyObject *self, PyObject *args) {
    git_repository *repo;
    Repository *py_repo;
    const char *path;
    unsigned int bare;
    int err;

    if (!PyArg_ParseTuple(args, "sI", &path, &bare))
        return NULL;

    err = git_repository_init(&repo, path, bare);
    if (err < 0) {
        Error_set_str(err, path);
        return NULL;
    }

    py_repo = PyObject_New(Repository, &RepositoryType);
    if (!py_repo) {
        git_repository_free(repo);
        return NULL;
    }

    py_repo->repo = repo;
    py_repo->index = NULL;
    return (PyObject*)py_repo;
};

static PyMethodDef module_methods[] = {
    {"init_repository", init_repository, METH_VARARGS,
     "Creates a new Git repository in the given folder."},
    {NULL}
};

PyMODINIT_FUNC
initpygit2(void)
{
    PyObject* m;

    GitError = PyErr_NewException("pygit2.GitError", NULL, NULL);

    RepositoryType.tp_new = PyType_GenericNew;
    if (PyType_Ready(&RepositoryType) < 0)
        return;

    /* Do not set 'tp_new' for Git objects. To create Git objects use the
     * Repository.create_XXX methods */
    if (PyType_Ready(&ObjectType) < 0)
        return;
    CommitType.tp_base = &ObjectType;
    if (PyType_Ready(&CommitType) < 0)
        return;
    TreeType.tp_base = &ObjectType;
    if (PyType_Ready(&TreeType) < 0)
        return;
    BlobType.tp_base = &ObjectType;
    if (PyType_Ready(&BlobType) < 0)
        return;
    TagType.tp_base = &ObjectType;
    if (PyType_Ready(&TagType) < 0)
        return;

    TreeEntryType.tp_new = PyType_GenericNew;
    if (PyType_Ready(&TreeEntryType) < 0)
        return;
    IndexType.tp_new = PyType_GenericNew;
    if (PyType_Ready(&IndexType) < 0)
        return;
    IndexEntryType.tp_new = PyType_GenericNew;
    if (PyType_Ready(&IndexEntryType) < 0)
        return;
    WalkerType.tp_new = PyType_GenericNew;
    if (PyType_Ready(&WalkerType) < 0)
        return;

    m = Py_InitModule3("pygit2", module_methods,
                       "Python bindings for libgit2.");

    if (m == NULL)
      return;

    Py_INCREF(GitError);
    PyModule_AddObject(m, "GitError", GitError);

    Py_INCREF(&RepositoryType);
    PyModule_AddObject(m, "Repository", (PyObject *)&RepositoryType);

    Py_INCREF(&ObjectType);
    PyModule_AddObject(m, "Object", (PyObject *)&ObjectType);

    Py_INCREF(&CommitType);
    PyModule_AddObject(m, "Commit", (PyObject *)&CommitType);

    Py_INCREF(&TreeEntryType);
    PyModule_AddObject(m, "TreeEntry", (PyObject *)&TreeEntryType);

    Py_INCREF(&TreeType);
    PyModule_AddObject(m, "Tree", (PyObject *)&TreeType);

    Py_INCREF(&BlobType);
    PyModule_AddObject(m, "Blob", (PyObject *)&BlobType);

    Py_INCREF(&TagType);
    PyModule_AddObject(m, "Tag", (PyObject *)&TagType);

    Py_INCREF(&IndexType);
    PyModule_AddObject(m, "Index", (PyObject *)&IndexType);

    Py_INCREF(&IndexEntryType);
    PyModule_AddObject(m, "IndexEntry", (PyObject *)&IndexEntryType);

    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);
    PyModule_AddIntConstant(m, "GIT_OBJ_BLOB", GIT_OBJ_BLOB);
    PyModule_AddIntConstant(m, "GIT_OBJ_TAG", GIT_OBJ_TAG);
    PyModule_AddIntConstant(m, "GIT_SORT_NONE", GIT_SORT_NONE);
    PyModule_AddIntConstant(m, "GIT_SORT_TOPOLOGICAL", GIT_SORT_TOPOLOGICAL);
    PyModule_AddIntConstant(m, "GIT_SORT_TIME", GIT_SORT_TIME);
    PyModule_AddIntConstant(m, "GIT_SORT_REVERSE", GIT_SORT_REVERSE);
}