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.
This commit is contained in:
161
pygit2/blame.py
Normal file
161
pygit2/blame.py
Normal file
@@ -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__()
|
@@ -5,7 +5,6 @@ typedef ... git_push;
|
|||||||
typedef ... git_cred;
|
typedef ... git_cred;
|
||||||
typedef ... git_object;
|
typedef ... git_object;
|
||||||
typedef ... git_tree;
|
typedef ... git_tree;
|
||||||
typedef ... git_signature;
|
|
||||||
typedef ... git_index;
|
typedef ... git_index;
|
||||||
typedef ... git_diff;
|
typedef ... git_diff;
|
||||||
typedef ... git_index_conflict_iterator;
|
typedef ... git_index_conflict_iterator;
|
||||||
@@ -29,6 +28,7 @@ typedef struct git_strarray {
|
|||||||
} git_strarray;
|
} git_strarray;
|
||||||
|
|
||||||
typedef int64_t git_off_t;
|
typedef int64_t git_off_t;
|
||||||
|
typedef int64_t git_time_t;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
GIT_OK = 0,
|
GIT_OK = 0,
|
||||||
@@ -55,6 +55,17 @@ typedef struct {
|
|||||||
int klass;
|
int klass;
|
||||||
} git_error;
|
} 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);
|
const git_error * giterr_last(void);
|
||||||
|
|
||||||
void git_strarray_free(git_strarray *array);
|
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_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_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);
|
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);
|
||||||
|
@@ -42,7 +42,8 @@ from .errors import check_error
|
|||||||
from .ffi import ffi, C
|
from .ffi import ffi, C
|
||||||
from .index import Index
|
from .index import Index
|
||||||
from .remote import Remote
|
from .remote import Remote
|
||||||
from .utils import to_bytes
|
from .blame import Blame
|
||||||
|
from .utils import to_bytes, to_str
|
||||||
|
|
||||||
|
|
||||||
class Repository(_Repository):
|
class Repository(_Repository):
|
||||||
@@ -370,6 +371,61 @@ class Repository(_Repository):
|
|||||||
"""
|
"""
|
||||||
C.git_repository_state_cleanup(self._repo)
|
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
|
# Index
|
||||||
#
|
#
|
||||||
|
399
src/blame.c
399
src/blame.c
@@ -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 <Python.h>
|
|
||||||
#include <structmember.h>
|
|
||||||
#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 <stdio.h>
|
|
||||||
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 */
|
|
||||||
};
|
|
38
src/blame.h
38
src/blame.h
@@ -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 <Python.h>
|
|
||||||
#include <git2.h>
|
|
||||||
#include "types.h"
|
|
||||||
|
|
||||||
PyObject* wrap_blame(git_blame *blame, Repository *repo);
|
|
||||||
|
|
||||||
#endif
|
|
@@ -62,10 +62,6 @@ extern PyTypeObject RemoteType;
|
|||||||
extern PyTypeObject RefspecType;
|
extern PyTypeObject RefspecType;
|
||||||
extern PyTypeObject NoteType;
|
extern PyTypeObject NoteType;
|
||||||
extern PyTypeObject NoteIterType;
|
extern PyTypeObject NoteIterType;
|
||||||
extern PyTypeObject BlameType;
|
|
||||||
extern PyTypeObject BlameIterType;
|
|
||||||
extern PyTypeObject BlameHunkType;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
PyDoc_STRVAR(discover_repository__doc__,
|
PyDoc_STRVAR(discover_repository__doc__,
|
||||||
@@ -337,11 +333,6 @@ moduleinit(PyObject* m)
|
|||||||
ADD_CONSTANT_INT(m, GIT_CONFIG_LEVEL_SYSTEM);
|
ADD_CONSTANT_INT(m, GIT_CONFIG_LEVEL_SYSTEM);
|
||||||
|
|
||||||
/* Blame */
|
/* 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_NORMAL)
|
||||||
ADD_CONSTANT_INT(m, GIT_BLAME_TRACK_COPIES_SAME_FILE)
|
ADD_CONSTANT_INT(m, GIT_BLAME_TRACK_COPIES_SAME_FILE)
|
||||||
ADD_CONSTANT_INT(m, GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES)
|
ADD_CONSTANT_INT(m, GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES)
|
||||||
|
@@ -36,7 +36,6 @@
|
|||||||
#include "note.h"
|
#include "note.h"
|
||||||
#include "repository.h"
|
#include "repository.h"
|
||||||
#include "branch.h"
|
#include "branch.h"
|
||||||
#include "blame.h"
|
|
||||||
#include "signature.h"
|
#include "signature.h"
|
||||||
#include <git2/odb_backend.h>
|
#include <git2/odb_backend.h>
|
||||||
|
|
||||||
@@ -1377,72 +1376,6 @@ Repository_lookup_note(Repository *self, PyObject* args)
|
|||||||
return (PyObject*) wrap_note(self, &annotated_id, ref);
|
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__,
|
PyDoc_STRVAR(Repository_reset__doc__,
|
||||||
"reset(oid, reset_type)\n"
|
"reset(oid, reset_type)\n"
|
||||||
"\n"
|
"\n"
|
||||||
@@ -1507,7 +1440,6 @@ PyMethodDef Repository_methods[] = {
|
|||||||
METHOD(Repository, lookup_branch, METH_VARARGS),
|
METHOD(Repository, lookup_branch, METH_VARARGS),
|
||||||
METHOD(Repository, listall_branches, METH_VARARGS),
|
METHOD(Repository, listall_branches, METH_VARARGS),
|
||||||
METHOD(Repository, create_branch, METH_VARARGS),
|
METHOD(Repository, create_branch, METH_VARARGS),
|
||||||
METHOD(Repository, blame, METH_VARARGS | METH_KEYWORDS),
|
|
||||||
METHOD(Repository, reset, METH_VARARGS),
|
METHOD(Repository, reset, METH_VARARGS),
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
23
src/types.h
23
src/types.h
@@ -185,27 +185,4 @@ typedef struct {
|
|||||||
char *encoding;
|
char *encoding;
|
||||||
} Signature;
|
} 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
|
#endif
|
||||||
|
@@ -31,7 +31,7 @@ from __future__ import absolute_import
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import unittest
|
import unittest
|
||||||
import pygit2
|
import pygit2
|
||||||
from pygit2 import Signature
|
from pygit2 import Signature, Oid
|
||||||
from pygit2 import GIT_DIFF_INCLUDE_UNMODIFIED
|
from pygit2 import GIT_DIFF_INCLUDE_UNMODIFIED
|
||||||
from pygit2 import GIT_DIFF_IGNORE_WHITESPACE, GIT_DIFF_IGNORE_WHITESPACE_EOL
|
from pygit2 import GIT_DIFF_IGNORE_WHITESPACE, GIT_DIFF_IGNORE_WHITESPACE_EOL
|
||||||
from . import utils
|
from . import utils
|
||||||
@@ -41,13 +41,13 @@ from datetime import datetime
|
|||||||
PATH = 'hello.txt'
|
PATH = 'hello.txt'
|
||||||
|
|
||||||
HUNKS = [
|
HUNKS = [
|
||||||
('acecd5ea2924a4b900e7e149496e1f4b57976e51', 1,
|
(Oid(hex='acecd5ea2924a4b900e7e149496e1f4b57976e51'), 1,
|
||||||
Signature('J. David Ibañez', 'jdavid@itaapy.com',
|
Signature('J. David Ibañez', 'jdavid@itaapy.com',
|
||||||
1297179898, 60, encoding='utf-8'), True),
|
1297179898, 60, encoding='utf-8'), True),
|
||||||
('6aaa262e655dd54252e5813c8e5acd7780ed097d', 2,
|
(Oid(hex='6aaa262e655dd54252e5813c8e5acd7780ed097d'), 2,
|
||||||
Signature('J. David Ibañez', 'jdavid@itaapy.com',
|
Signature('J. David Ibañez', 'jdavid@itaapy.com',
|
||||||
1297696877, 60, encoding='utf-8'), False),
|
1297696877, 60, encoding='utf-8'), False),
|
||||||
('4ec4389a8068641da2d6578db0419484972284c8', 3,
|
(Oid(hex='4ec4389a8068641da2d6578db0419484972284c8'), 3,
|
||||||
Signature('J. David Ibañez', 'jdavid@itaapy.com',
|
Signature('J. David Ibañez', 'jdavid@itaapy.com',
|
||||||
1297696908, 60, encoding='utf-8'), False)
|
1297696908, 60, encoding='utf-8'), False)
|
||||||
]
|
]
|
||||||
|
Reference in New Issue
Block a user