From cd084259271d156c7699adf123a3ea01c08b1c6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 27 Aug 2014 18:30:30 +0200 Subject: [PATCH] Move blame to cffi This requires fairly little work on the pygit2 side to kick off all the searching on the libgit2 side, so it's a fairly good candidate. This changes the return value for the commit ids to Oid instead of strings, which is what we generally try to return. --- pygit2/blame.py | 161 +++++++++++++++++ pygit2/decl.h | 54 +++++- pygit2/repository.py | 58 ++++++- src/blame.c | 399 ------------------------------------------- src/blame.h | 38 ----- src/pygit2.c | 9 - src/repository.c | 68 -------- src/types.h | 23 --- test/test_blame.py | 8 +- 9 files changed, 275 insertions(+), 543 deletions(-) create mode 100644 pygit2/blame.py delete mode 100644 src/blame.c delete mode 100644 src/blame.h diff --git a/pygit2/blame.py b/pygit2/blame.py new file mode 100644 index 000000000..3e1afd9d4 --- /dev/null +++ b/pygit2/blame.py @@ -0,0 +1,161 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2010-2014 The pygit2 contributors +# +# 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. + +# Import from the future +from __future__ import absolute_import, unicode_literals + +# Import from pygit2 +from .errors import check_error +from .ffi import ffi, C +from .utils import to_bytes, is_string, to_str +from _pygit2 import Signature, Oid + +def wrap_signature(csig): + if not csig: + return None + + return Signature(ffi.string(csig.name).decode('utf-8'), + ffi.string(csig.email).decode('utf-8'), + csig.when.time, csig.when.offset, 'utf-8') + +class BlameHunk(object): + + @classmethod + def _from_c(cls, blame, ptr): + hunk = cls.__new__(cls) + hunk._blame = blame + hunk._hunk = ptr + return hunk + + @property + def lines_in_hunk(self): + """Number of lines""" + return self._hunk.lines_in_hunk + + @property + def boundary(self): + """Tracked to a boundary commit""" + # Casting directly to bool via cffi does not seem to work + return int(ffi.cast('int', self._hunk.boundary)) != 0 + + @property + def final_start_line_number(self): + """Final start line number""" + return self._hunk.final_start_line_number + + @property + def final_committer(self): + """Final committer""" + return wrap_signature(self._hunk.final_signature) + + @property + def final_commit_id(self): + return Oid(raw=bytes(ffi.buffer(ffi.addressof(self._hunk, 'final_commit_id'))[:])) + + @property + def orig_start_line_number(self): + """Origin start line number""" + return self._hunk.orig_start_line_number + + @property + def orig_committer(self): + """Original committer""" + return wrap_signature(self._hunk.orig_signature) + + @property + def orig_commit_id(self): + return Oid(raw=bytes(ffi.buffer(ffi.addressof(self._hunk, 'orig_commit_id'))[:])) + + @property + def orig_path(self): + """Original path""" + path = self._hunk.orig_path + if not path: + return None + + return ffi.string(path).decode() + + +class Blame(object): + + @classmethod + def _from_c(cls, repo, ptr): + blame = cls.__new__(cls) + blame._repo = repo + blame._blame = ptr + return blame + + def __del__(self): + C.git_blame_free(self._blame) + + def __len__(self): + return C.git_blame_get_hunk_count(self._blame) + + def __getitem__(self, index): + chunk = C.git_blame_get_hunk_byindex(self._blame, index) + if not chunk: + raise IndexError + + return BlameHunk._from_c(self, chunk) + + def for_line(self, line_no): + """for_line(line_no) -> BlameHunk + + Returns the blame hunk data for a given line given its number + in the current Blame. + + Arguments: + + line_no + Line number, starts at 1. + """ + if line_no < 0: + raise IndexError + + chunk = C.git_blame_get_hunk_byline(self._blame, line_no) + if not chunk: + raise IndexError + + return BlameHunk._from_c(self, chunk) + +class BlameIterator(object): + def __init__(self, blame): + self._count = len(blame) + self._index = 0 + self._blame = blame + + def __next__(self): + if self._index >= self._count: + raise StopIteration + + hunk = self._blame[self._blame] + self._index += 1 + + return hunk + + def next(self): + return self.__next__() diff --git a/pygit2/decl.h b/pygit2/decl.h index 4e3df66b5..825ece79c 100644 --- a/pygit2/decl.h +++ b/pygit2/decl.h @@ -5,7 +5,6 @@ typedef ... git_push; typedef ... git_cred; typedef ... git_object; typedef ... git_tree; -typedef ... git_signature; typedef ... git_index; typedef ... git_diff; typedef ... git_index_conflict_iterator; @@ -29,6 +28,7 @@ typedef struct git_strarray { } git_strarray; typedef int64_t git_off_t; +typedef int64_t git_time_t; typedef enum { GIT_OK = 0, @@ -55,6 +55,17 @@ typedef struct { int klass; } git_error; +typedef struct git_time { + git_time_t time; + int offset; +} git_time; + +typedef struct git_signature { + char *name; + char *email; + git_time when; +} git_signature; + const git_error * giterr_last(void); void git_strarray_free(git_strarray *array); @@ -506,3 +517,44 @@ int git_index_conflict_iterator_new(git_index_conflict_iterator **iterator_out, int git_index_conflict_get(const git_index_entry **ancestor_out, const git_index_entry **our_out, const git_index_entry **their_out, git_index *index, const char *path); int git_index_conflict_next(const git_index_entry **ancestor_out, const git_index_entry **our_out, const git_index_entry **their_out, git_index_conflict_iterator *iterator); int git_index_conflict_remove(git_index *index, const char *path); + +/* + * git_blame + */ + +typedef ... git_blame; + +typedef struct git_blame_options { + unsigned int version; + + uint32_t flags; + uint16_t min_match_characters; + git_oid newest_commit; + git_oid oldest_commit; + uint32_t min_line; + uint32_t max_line; +} git_blame_options; + +#define GIT_BLAME_OPTIONS_VERSION ... + +typedef struct git_blame_hunk { + uint16_t lines_in_hunk; + + git_oid final_commit_id; + uint16_t final_start_line_number; + git_signature *final_signature; + + git_oid orig_commit_id; + const char *orig_path; + uint16_t orig_start_line_number; + git_signature *orig_signature; + + char boundary; +} git_blame_hunk; + +int git_blame_init_options(git_blame_options *opts, unsigned int version); +uint32_t git_blame_get_hunk_count(git_blame *blame); +const git_blame_hunk *git_blame_get_hunk_byindex(git_blame *blame, uint32_t index); +const git_blame_hunk *git_blame_get_hunk_byline(git_blame *blame, uint32_t lineno); +int git_blame_file(git_blame **out, git_repository *repo, const char *path, git_blame_options *options); +void git_blame_free(git_blame *blame); diff --git a/pygit2/repository.py b/pygit2/repository.py index ac284b9be..fd50d74cb 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -42,7 +42,8 @@ from .ffi import ffi, C from .index import Index from .remote import Remote -from .utils import to_bytes +from .blame import Blame +from .utils import to_bytes, to_str class Repository(_Repository): @@ -370,6 +371,61 @@ def state_cleanup(self): """ C.git_repository_state_cleanup(self._repo) + # + # blame + # + def blame(self, path, flags=None, min_match_characters=None, newest_commit=None, oldest_commit=None, min_line=None, max_line=None): + """blame(path, [flags, min_match_characters, newest_commit, oldest_commit,\n" + min_line, max_line]) -> Blame + + Get the blame for a single file. + + Arguments: + + path + Path to the file to blame. + flags + A GIT_BLAME_* constant. + min_match_characters + The number of alphanum chars that must be detected as moving/copying + within a file for it to associate those lines with the parent commit. + newest_commit + The id of the newest commit to consider. + oldest_commit + The id of the oldest commit to consider. + min_line + The first line in the file to blame. + max_line + The last line in the file to blame. + + Examples:: + + repo.blame('foo.c', flags=GIT_BLAME_TRACK_COPIES_SAME_FILE)"); + """ + + options = ffi.new('git_blame_options *') + C.git_blame_init_options(options, C.GIT_BLAME_OPTIONS_VERSION) + if min_match_characters: + options.min_match_characters = min_match_characters + if newest_commit: + if not isinstance(newest_commit, Oid): + newest_commit = Oid(hex=newest_commit) + ffi.buffer(ffi.addressof(options, 'newest_commit'))[:] = newest_commit.raw + if oldest_commit: + if not isinstance(oldest_commit, Oid): + oldest_commit = Oid(hex=oldest_commit) + ffi.buffer(ffi.addressof(options, 'oldest_commit'))[:] = oldest_commit.raw + if min_line: + options.min_line = min_line + if max_line: + options.max_line = max_line + + cblame = ffi.new('git_blame **') + err = C.git_blame_file(cblame, self._repo, to_bytes(path), options) + check_error(err) + + return Blame._from_c(self, cblame[0]) + # # Index # diff --git a/src/blame.c b/src/blame.c deleted file mode 100644 index f1c2f6292..000000000 --- a/src/blame.c +++ /dev/null @@ -1,399 +0,0 @@ -/* - * Copyright 2010-2014 The pygit2 contributors - * - * 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. - */ - -#define PY_SSIZE_T_CLEAN -#include -#include -#include "error.h" -#include "types.h" -#include "utils.h" -#include "signature.h" -#include "blame.h" - -extern PyObject *GitError; - -extern PyTypeObject BlameType; -extern PyTypeObject BlameIterType; -extern PyTypeObject BlameHunkType; - -PyObject* -wrap_blame(git_blame *blame, Repository *repo) -{ - Blame *py_blame; - - py_blame = PyObject_New(Blame, &BlameType); - if (py_blame) { - Py_INCREF(repo); - py_blame->repo = repo; - py_blame->blame = blame; - } - - return (PyObject*) py_blame; -} - -#include -PyObject* -wrap_blame_hunk(const git_blame_hunk *hunk, Blame *blame) -{ - BlameHunk *py_hunk = NULL; - int err; - - if (!hunk) - Py_RETURN_NONE; - - py_hunk = PyObject_New(BlameHunk, &BlameHunkType); - if (py_hunk == NULL) - return NULL; - - py_hunk->lines_in_hunk = hunk->lines_in_hunk; - py_hunk->final_commit_id = git_oid_allocfmt(&hunk->final_commit_id); - py_hunk->final_start_line_number = hunk->final_start_line_number; - - py_hunk->final_signature = NULL; - if (hunk->final_signature) { - err = git_signature_dup(&py_hunk->final_signature, - hunk->final_signature); - if (err < 0) - return Error_set(err); - } - - py_hunk->orig_commit_id = git_oid_allocfmt(&hunk->orig_commit_id); - py_hunk->orig_path = hunk->orig_path != NULL ? - strdup(hunk->orig_path) : NULL; - py_hunk->orig_start_line_number = hunk->orig_start_line_number; - - py_hunk->orig_signature = NULL; - if (hunk->orig_signature) { - err = git_signature_dup(&py_hunk->orig_signature, - hunk->orig_signature); - if (err < 0) - return Error_set(err); - } - - py_hunk->boundary = hunk->boundary; - return (PyObject*) py_hunk; -} - -PyDoc_STRVAR(BlameHunk_final_committer__doc__, "Final committer."); - -PyObject * -BlameHunk_final_committer__get__(BlameHunk *self) -{ - if (!self->final_signature) - Py_RETURN_NONE; - - return build_signature((Object*) self, self->final_signature, "utf-8"); -} - -PyDoc_STRVAR(BlameHunk_orig_committer__doc__, "Origin committer."); - -PyObject * -BlameHunk_orig_committer__get__(BlameHunk *self) -{ - if (!self->orig_signature) - Py_RETURN_NONE; - - return build_signature((Object*) self, self->orig_signature, "utf-8"); -} - -static int -BlameHunk_init(BlameHunk *self, PyObject *args, PyObject *kwds) -{ - self->final_commit_id = NULL; - self->final_signature = NULL; - self->orig_commit_id = NULL; - self->orig_path = NULL; - self->orig_signature = NULL; - - return 0; -} - -static void -BlameHunk_dealloc(BlameHunk *self) -{ - free(self->final_commit_id); - if (self->final_signature) - git_signature_free(self->final_signature); - free(self->orig_commit_id); - if (self->orig_path) - free(self->orig_path); - if (self->orig_signature) - git_signature_free(self->orig_signature); - PyObject_Del(self); -} - -PyMemberDef BlameHunk_members[] = { - MEMBER(BlameHunk, lines_in_hunk, T_UINT, "Number of lines."), - MEMBER(BlameHunk, final_commit_id, T_STRING, "Last changed oid."), - MEMBER(BlameHunk, final_start_line_number, T_UINT, "final start line no."), - MEMBER(BlameHunk, orig_commit_id, T_STRING, "oid where hunk was found."), - MEMBER(BlameHunk, orig_path, T_STRING, "Origin path."), - MEMBER(BlameHunk, orig_start_line_number, T_UINT, "Origin start line no."), - MEMBER(BlameHunk, boundary, T_BOOL, "Tracked to a boundary commit."), - {NULL} -}; - -PyGetSetDef BlameHunk_getseters[] = { - GETTER(BlameHunk, final_committer), - GETTER(BlameHunk, orig_committer), - {NULL} -}; - -PyDoc_STRVAR(BlameHunk__doc__, "Blame Hunk object."); - -PyTypeObject BlameHunkType = { - PyVarObject_HEAD_INIT(NULL, 0) - "_pygit2.BlameHunk", /* tp_name */ - sizeof(BlameHunk), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)BlameHunk_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 */ - BlameHunk__doc__, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - 0, /* tp_methods */ - BlameHunk_members, /* tp_members */ - BlameHunk_getseters, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - (initproc)BlameHunk_init, /* tp_init */ - 0, /* tp_alloc */ - 0, /* tp_new */ -}; - - -PyObject * -BlameIter_iternext(BlameIter *self) -{ - if (self->i < self->n) - return wrap_blame_hunk(git_blame_get_hunk_byindex( - self->blame->blame, self->i++), self->blame); - - PyErr_SetNone(PyExc_StopIteration); - return NULL; -} - -static void -BlameIter_dealloc(BlameIter *self) -{ - Py_CLEAR(self->blame); - PyObject_Del(self); -} - - -PyDoc_STRVAR(BlameIter__doc__, "Blame iterator object."); - -PyTypeObject BlameIterType = { - PyVarObject_HEAD_INIT(NULL, 0) - "_pygit2.BlameIter", /* tp_name */ - sizeof(BlameIter), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)BlameIter_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 */ - BlameIter__doc__, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - PyObject_SelfIter, /* tp_iter */ - (iternextfunc) BlameIter_iternext, /* tp_iternext */ -}; - - -PyObject * -Blame_iter(Blame *self) -{ - BlameIter *iter; - - iter = PyObject_New(BlameIter, &BlameIterType); - if (iter != NULL) { - Py_INCREF(self); - iter->blame = self; - iter->i = 0; - iter->n = git_blame_get_hunk_count(self->blame); - } - return (PyObject*)iter; -} - -Py_ssize_t -Blame_len(Blame *self) -{ - assert(self->blame); - return (Py_ssize_t)git_blame_get_hunk_count(self->blame); -} - -PyObject * -Blame_getitem(Blame *self, PyObject *value) -{ - size_t i; - const git_blame_hunk *hunk; - - if (PyLong_Check(value) < 0) { - PyErr_SetObject(PyExc_IndexError, value); - return NULL; - } - - i = PyLong_AsUnsignedLong(value); - if (PyErr_Occurred()) { - PyErr_SetObject(PyExc_IndexError, value); - return NULL; - } - - hunk = git_blame_get_hunk_byindex(self->blame, i); - if (!hunk) { - PyErr_SetObject(PyExc_IndexError, value); - return NULL; - } - - return wrap_blame_hunk(hunk, self); -} - -PyDoc_STRVAR(Blame_for_line__doc__, - "for_line(line_no) -> hunk\n" - "\n" - "Returns the blame hunk data for the given \"line_no\" in blame.\n" - "\n" - "Arguments:\n" - "\n" - "line_no\n" - " Line number, countings starts with 1."); - -PyObject * -Blame_for_line(Blame *self, PyObject *args) -{ - size_t line_no; - const git_blame_hunk *hunk; - - if (!PyArg_ParseTuple(args, "I", &line_no)) - return NULL; - - hunk = git_blame_get_hunk_byline(self->blame, line_no); - if (!hunk) { - PyErr_SetObject(PyExc_IndexError, args); - return NULL; - } - - return wrap_blame_hunk(hunk, self); -} - -static void -Blame_dealloc(Blame *self) -{ - git_blame_free(self->blame); - Py_CLEAR(self->repo); - PyObject_Del(self); -} - -PyMappingMethods Blame_as_mapping = { - (lenfunc)Blame_len, /* mp_length */ - (binaryfunc)Blame_getitem, /* mp_subscript */ - 0, /* mp_ass_subscript */ -}; - -static PyMethodDef Blame_methods[] = { - METHOD(Blame, for_line, METH_VARARGS), - {NULL} -}; - - -PyDoc_STRVAR(Blame__doc__, "Blame objects."); - -PyTypeObject BlameType = { - PyVarObject_HEAD_INIT(NULL, 0) - "_pygit2.Blame", /* tp_name */ - sizeof(Blame), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)Blame_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 */ - &Blame_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, /* tp_flags */ - Blame__doc__, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - (getiterfunc)Blame_iter, /* tp_iter */ - 0, /* tp_iternext */ - Blame_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 */ -}; diff --git a/src/blame.h b/src/blame.h deleted file mode 100644 index 20bff683e..000000000 --- a/src/blame.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2010-2014 The pygit2 contributors - * - * 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. - */ - -#ifndef INCLUDE_pygit2_blame_h -#define INCLUDE_pygit2_blame_h - -#define PY_SSIZE_T_CLEAN -#include -#include -#include "types.h" - -PyObject* wrap_blame(git_blame *blame, Repository *repo); - -#endif diff --git a/src/pygit2.c b/src/pygit2.c index 35f87b5e2..767ae2e98 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -62,10 +62,6 @@ extern PyTypeObject RemoteType; extern PyTypeObject RefspecType; extern PyTypeObject NoteType; extern PyTypeObject NoteIterType; -extern PyTypeObject BlameType; -extern PyTypeObject BlameIterType; -extern PyTypeObject BlameHunkType; - PyDoc_STRVAR(discover_repository__doc__, @@ -337,11 +333,6 @@ moduleinit(PyObject* m) ADD_CONSTANT_INT(m, GIT_CONFIG_LEVEL_SYSTEM); /* Blame */ - INIT_TYPE(BlameType, NULL, NULL) - INIT_TYPE(BlameIterType, NULL, NULL) - INIT_TYPE(BlameHunkType, NULL, NULL) - ADD_TYPE(m, Blame) - ADD_TYPE(m, BlameHunk) ADD_CONSTANT_INT(m, GIT_BLAME_NORMAL) ADD_CONSTANT_INT(m, GIT_BLAME_TRACK_COPIES_SAME_FILE) ADD_CONSTANT_INT(m, GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES) diff --git a/src/repository.c b/src/repository.c index 2b99558f7..24ca33de4 100644 --- a/src/repository.c +++ b/src/repository.c @@ -36,7 +36,6 @@ #include "note.h" #include "repository.h" #include "branch.h" -#include "blame.h" #include "signature.h" #include @@ -1377,72 +1376,6 @@ Repository_lookup_note(Repository *self, PyObject* args) return (PyObject*) wrap_note(self, &annotated_id, ref); } -PyDoc_STRVAR(Repository_blame__doc__, - "blame(path, [flags, min_match_characters, newest_commit, oldest_commit,\n" - " min_line, max_line]) -> blame\n" - "\n" - "Get the blame for a single file.\n" - "\n" - "Arguments:\n" - "\n" - "path\n" - " A path to file to consider.\n" - "flags\n" - " A GIT_BLAME_* constant.\n" - "min_match_characters\n" - " The number of alphanum chars that must be detected as moving/copying\n" - " within a file for it to associate those lines with the parent commit.\n" - "newest_commit\n" - " The id of the newest commit to consider.\n" - "oldest_commit\n" - " The id of the oldest commit to consider.\n" - "min_line\n" - " The first line in the file to blame.\n" - "max_line\n" - " The last line in the file to blame.\n" - "\n" - "Examples::\n" - "\n" - " repo.blame('foo.c', flags=GIT_BLAME_TRACK_COPIES_SAME_FILE)"); - -PyObject * -Repository_blame(Repository *self, PyObject *args, PyObject *kwds) -{ - git_blame_options opts = GIT_BLAME_OPTIONS_INIT; - git_blame *blame; - char *path; - PyObject *value1 = NULL; - PyObject *value2 = NULL; - int err; - char *keywords[] = {"path", "flags", "min_match_characters", "newest_commit", - "oldest_commit", "min_line", "max_line", NULL}; - - if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|IHO!O!II", keywords, - &path, &opts.flags, - &opts.min_match_characters, - &OidType, &value1, - &OidType, &value2, - &opts.min_line, &opts.max_line)) - return NULL; - - if (value1) { - err = py_oid_to_git_oid_expand(self->repo, value1, &opts.newest_commit); - if (err < 0) - return NULL; - } - if (value2) { - err = py_oid_to_git_oid_expand(self->repo, value2, &opts.oldest_commit); - if (err < 0) - return NULL; - } - - err = git_blame_file(&blame, self->repo, path, &opts); - if (err < 0) - return Error_set(err); - - return wrap_blame(blame, self); -} - PyDoc_STRVAR(Repository_reset__doc__, "reset(oid, reset_type)\n" "\n" @@ -1507,7 +1440,6 @@ PyMethodDef Repository_methods[] = { METHOD(Repository, lookup_branch, METH_VARARGS), METHOD(Repository, listall_branches, METH_VARARGS), METHOD(Repository, create_branch, METH_VARARGS), - METHOD(Repository, blame, METH_VARARGS | METH_KEYWORDS), METHOD(Repository, reset, METH_VARARGS), {NULL} }; diff --git a/src/types.h b/src/types.h index e69b489eb..58e9e991c 100644 --- a/src/types.h +++ b/src/types.h @@ -185,27 +185,4 @@ typedef struct { char *encoding; } Signature; -/* git_blame */ -SIMPLE_TYPE(Blame, git_blame, blame) - -typedef struct { - PyObject_HEAD - Blame* blame; - size_t i; - size_t n; -} BlameIter; - -typedef struct { - PyObject_HEAD - unsigned lines_in_hunk; - char* final_commit_id; - unsigned final_start_line_number; - git_signature* final_signature; - char* orig_commit_id; - char* orig_path; - unsigned orig_start_line_number; - git_signature* orig_signature; - char boundary; -} BlameHunk; - #endif diff --git a/test/test_blame.py b/test/test_blame.py index cbdba9c6a..3e1bf7e77 100644 --- a/test/test_blame.py +++ b/test/test_blame.py @@ -31,7 +31,7 @@ from __future__ import unicode_literals import unittest import pygit2 -from pygit2 import Signature +from pygit2 import Signature, Oid from pygit2 import GIT_DIFF_INCLUDE_UNMODIFIED from pygit2 import GIT_DIFF_IGNORE_WHITESPACE, GIT_DIFF_IGNORE_WHITESPACE_EOL from . import utils @@ -41,13 +41,13 @@ PATH = 'hello.txt' HUNKS = [ - ('acecd5ea2924a4b900e7e149496e1f4b57976e51', 1, + (Oid(hex='acecd5ea2924a4b900e7e149496e1f4b57976e51'), 1, Signature('J. David Ibañez', 'jdavid@itaapy.com', 1297179898, 60, encoding='utf-8'), True), - ('6aaa262e655dd54252e5813c8e5acd7780ed097d', 2, + (Oid(hex='6aaa262e655dd54252e5813c8e5acd7780ed097d'), 2, Signature('J. David Ibañez', 'jdavid@itaapy.com', 1297696877, 60, encoding='utf-8'), False), - ('4ec4389a8068641da2d6578db0419484972284c8', 3, + (Oid(hex='4ec4389a8068641da2d6578db0419484972284c8'), 3, Signature('J. David Ibañez', 'jdavid@itaapy.com', 1297696908, 60, encoding='utf-8'), False) ]