Skip to content

Commit 629e318

Browse files
committed
Add Blame support
1 parent 9da4d4f commit 629e318

File tree

7 files changed

+641
-0
lines changed

7 files changed

+641
-0
lines changed

src/blame.c

+384
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,384 @@
1+
/*
2+
* Copyright 2010-2013 The pygit2 contributors
3+
*
4+
* This file is free software; you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License, version 2,
6+
* as published by the Free Software Foundation.
7+
*
8+
* In addition to the permissions in the GNU General Public License,
9+
* the authors give you unlimited permission to link the compiled
10+
* version of this file into combinations with other programs,
11+
* and to distribute those combinations without any restriction
12+
* coming from the use of this file. (The General Public License
13+
* restrictions do apply in other respects; for example, they cover
14+
* modification of the file, and distribution when not linked into
15+
* a combined executable.)
16+
*
17+
* This file is distributed in the hope that it will be useful, but
18+
* WITHOUT ANY WARRANTY; without even the implied warranty of
19+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20+
* General Public License for more details.
21+
*
22+
* You should have received a copy of the GNU General Public License
23+
* along with this program; see the file COPYING. If not, write to
24+
* the Free Software Foundation, 51 Franklin Street, Fifth Floor,
25+
* Boston, MA 02110-1301, USA.
26+
*/
27+
28+
#define PY_SSIZE_T_CLEAN
29+
#include <Python.h>
30+
#include <structmember.h>
31+
#include "error.h"
32+
#include "types.h"
33+
#include "utils.h"
34+
#include "signature.h"
35+
#include "blame.h"
36+
37+
extern PyObject *GitError;
38+
39+
extern PyTypeObject BlameType;
40+
extern PyTypeObject BlameIterType;
41+
extern PyTypeObject BlameHunkType;
42+
43+
PyObject*
44+
wrap_blame(git_blame *blame, Repository *repo)
45+
{
46+
Blame *py_blame;
47+
48+
py_blame = PyObject_New(Blame, &BlameType);
49+
if (py_blame) {
50+
Py_INCREF(repo);
51+
py_blame->repo = repo;
52+
py_blame->blame = blame;
53+
}
54+
55+
return (PyObject*) py_blame;
56+
}
57+
58+
#include <stdio.h>
59+
PyObject*
60+
wrap_blame_hunk(const git_blame_hunk *hunk, Blame *blame)
61+
{
62+
BlameHunk *py_hunk = NULL;
63+
64+
if (!hunk)
65+
Py_RETURN_NONE;
66+
67+
py_hunk = PyObject_New(BlameHunk, &BlameHunkType);
68+
if (py_hunk != NULL) {
69+
py_hunk->lines_in_hunk = hunk->lines_in_hunk;
70+
py_hunk->final_commit_id = git_oid_allocfmt(&hunk->final_commit_id);
71+
py_hunk->final_start_line_number = hunk->final_start_line_number;
72+
py_hunk->final_signature = hunk->final_signature != NULL ?
73+
git_signature_dup(hunk->final_signature) : NULL;
74+
py_hunk->orig_commit_id = git_oid_allocfmt(&hunk->orig_commit_id);
75+
py_hunk->orig_path = hunk->orig_path != NULL ?
76+
strdup(hunk->orig_path) : NULL;
77+
py_hunk->orig_start_line_number = hunk->orig_start_line_number;
78+
py_hunk->orig_signature = hunk->orig_signature != NULL ?
79+
git_signature_dup(hunk->orig_signature) : NULL;
80+
py_hunk->boundary = hunk->boundary;
81+
}
82+
83+
return (PyObject*) py_hunk;
84+
}
85+
86+
PyDoc_STRVAR(BlameHunk_final_committer__doc__, "Final committer.");
87+
88+
PyObject *
89+
BlameHunk_final_committer__get__(BlameHunk *self)
90+
{
91+
if (!self->final_signature)
92+
Py_RETURN_NONE;
93+
94+
return build_signature((Object*) self, self->final_signature, "utf-8");
95+
}
96+
97+
PyDoc_STRVAR(BlameHunk_orig_committer__doc__, "Origin committer.");
98+
99+
PyObject *
100+
BlameHunk_orig_committer__get__(BlameHunk *self)
101+
{
102+
if (!self->orig_signature)
103+
Py_RETURN_NONE;
104+
105+
return build_signature((Object*) self, self->orig_signature, "utf-8");
106+
}
107+
108+
static int
109+
BlameHunk_init(BlameHunk *self, PyObject *args, PyObject *kwds)
110+
{
111+
self->final_commit_id = NULL;
112+
self->final_signature = NULL;
113+
self->orig_commit_id = NULL;
114+
self->orig_path = NULL;
115+
self->orig_signature = NULL;
116+
117+
return 0;
118+
}
119+
120+
static void
121+
BlameHunk_dealloc(BlameHunk *self)
122+
{
123+
free(self->final_commit_id);
124+
if (self->final_signature)
125+
git_signature_free(self->final_signature);
126+
free(self->orig_commit_id);
127+
if (self->orig_path)
128+
free(self->orig_path);
129+
if (self->orig_signature)
130+
git_signature_free(self->orig_signature);
131+
PyObject_Del(self);
132+
}
133+
134+
PyMemberDef BlameHunk_members[] = {
135+
MEMBER(BlameHunk, lines_in_hunk, T_UINT, "Number of lines."),
136+
MEMBER(BlameHunk, final_commit_id, T_STRING, "Last changed oid."),
137+
MEMBER(BlameHunk, final_start_line_number, T_UINT, "final start line no."),
138+
MEMBER(BlameHunk, orig_commit_id, T_STRING, "oid where hunk was found."),
139+
MEMBER(BlameHunk, orig_path, T_STRING, "Origin path."),
140+
MEMBER(BlameHunk, orig_start_line_number, T_UINT, "Origin start line no."),
141+
MEMBER(BlameHunk, boundary, T_BOOL, "Tracked to a boundary commit."),
142+
{NULL}
143+
};
144+
145+
PyGetSetDef BlameHunk_getseters[] = {
146+
GETTER(BlameHunk, final_committer),
147+
GETTER(BlameHunk, orig_committer),
148+
{NULL}
149+
};
150+
151+
PyDoc_STRVAR(BlameHunk__doc__, "Blame Hunk object.");
152+
153+
PyTypeObject BlameHunkType = {
154+
PyVarObject_HEAD_INIT(NULL, 0)
155+
"_pygit2.BlameHunk", /* tp_name */
156+
sizeof(BlameHunk), /* tp_basicsize */
157+
0, /* tp_itemsize */
158+
(destructor)BlameHunk_dealloc, /* tp_dealloc */
159+
0, /* tp_print */
160+
0, /* tp_getattr */
161+
0, /* tp_setattr */
162+
0, /* tp_compare */
163+
0, /* tp_repr */
164+
0, /* tp_as_number */
165+
0, /* tp_as_sequence */
166+
0, /* tp_as_mapping */
167+
0, /* tp_hash */
168+
0, /* tp_call */
169+
0, /* tp_str */
170+
0, /* tp_getattro */
171+
0, /* tp_setattro */
172+
0, /* tp_as_buffer */
173+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
174+
BlameHunk__doc__, /* tp_doc */
175+
0, /* tp_traverse */
176+
0, /* tp_clear */
177+
0, /* tp_richcompare */
178+
0, /* tp_weaklistoffset */
179+
0, /* tp_iter */
180+
0, /* tp_iternext */
181+
0, /* tp_methods */
182+
BlameHunk_members, /* tp_members */
183+
BlameHunk_getseters, /* tp_getset */
184+
0, /* tp_base */
185+
0, /* tp_dict */
186+
0, /* tp_descr_get */
187+
0, /* tp_descr_set */
188+
0, /* tp_dictoffset */
189+
(initproc)BlameHunk_init, /* tp_init */
190+
0, /* tp_alloc */
191+
0, /* tp_new */
192+
};
193+
194+
195+
PyObject *
196+
BlameIter_iternext(BlameIter *self)
197+
{
198+
if (self->i < self->n)
199+
return wrap_blame_hunk(git_blame_get_hunk_byindex(
200+
self->blame->blame, self->i++), self->blame);
201+
202+
PyErr_SetNone(PyExc_StopIteration);
203+
return NULL;
204+
}
205+
206+
static void
207+
BlameIter_dealloc(BlameIter *self)
208+
{
209+
Py_CLEAR(self->blame);
210+
PyObject_Del(self);
211+
}
212+
213+
214+
PyDoc_STRVAR(BlameIter__doc__, "Blame iterator object.");
215+
216+
PyTypeObject BlameIterType = {
217+
PyVarObject_HEAD_INIT(NULL, 0)
218+
"_pygit2.BlameIter", /* tp_name */
219+
sizeof(BlameIter), /* tp_basicsize */
220+
0, /* tp_itemsize */
221+
(destructor)BlameIter_dealloc, /* tp_dealloc */
222+
0, /* tp_print */
223+
0, /* tp_getattr */
224+
0, /* tp_setattr */
225+
0, /* tp_compare */
226+
0, /* tp_repr */
227+
0, /* tp_as_number */
228+
0, /* tp_as_sequence */
229+
0, /* tp_as_mapping */
230+
0, /* tp_hash */
231+
0, /* tp_call */
232+
0, /* tp_str */
233+
0, /* tp_getattro */
234+
0, /* tp_setattro */
235+
0, /* tp_as_buffer */
236+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
237+
BlameIter__doc__, /* tp_doc */
238+
0, /* tp_traverse */
239+
0, /* tp_clear */
240+
0, /* tp_richcompare */
241+
0, /* tp_weaklistoffset */
242+
PyObject_SelfIter, /* tp_iter */
243+
(iternextfunc) BlameIter_iternext, /* tp_iternext */
244+
};
245+
246+
247+
PyObject *
248+
Blame_iter(Blame *self)
249+
{
250+
BlameIter *iter;
251+
252+
iter = PyObject_New(BlameIter, &BlameIterType);
253+
if (iter != NULL) {
254+
Py_INCREF(self);
255+
iter->blame = self;
256+
iter->i = 0;
257+
iter->n = git_blame_get_hunk_count(self->blame);
258+
}
259+
return (PyObject*)iter;
260+
}
261+
262+
Py_ssize_t
263+
Blame_len(Blame *self)
264+
{
265+
assert(self->blame);
266+
return (Py_ssize_t)git_blame_get_hunk_count(self->blame);
267+
}
268+
269+
PyObject *
270+
Blame_getitem(Blame *self, PyObject *value)
271+
{
272+
size_t i;
273+
const git_blame_hunk *hunk;
274+
275+
if (PyLong_Check(value) < 0) {
276+
PyErr_SetObject(PyExc_IndexError, value);
277+
return NULL;
278+
}
279+
280+
i = PyLong_AsUnsignedLong(value);
281+
if (PyErr_Occurred()) {
282+
PyErr_SetObject(PyExc_IndexError, value);
283+
return NULL;
284+
}
285+
286+
hunk = git_blame_get_hunk_byindex(self->blame, i);
287+
if (!hunk) {
288+
PyErr_SetObject(PyExc_IndexError, value);
289+
return NULL;
290+
}
291+
292+
return wrap_blame_hunk(hunk, self);
293+
}
294+
295+
PyDoc_STRVAR(Blame_for_line__doc__,
296+
"for_line(line_no) -> hunk\n"
297+
"\n"
298+
"Returns the blame hunk data for the given \"line_no\" in blame.\n"
299+
"\n"
300+
"Arguments:\n"
301+
"\n"
302+
"line_no\n"
303+
" Line number, countings starts with 1.");
304+
305+
PyObject *
306+
Blame_for_line(Blame *self, PyObject *args)
307+
{
308+
size_t line_no;
309+
const git_blame_hunk *hunk;
310+
311+
if (!PyArg_ParseTuple(args, "I", &line_no))
312+
return NULL;
313+
314+
hunk = git_blame_get_hunk_byline(self->blame, line_no);
315+
if (!hunk) {
316+
PyErr_SetObject(PyExc_IndexError, args);
317+
return NULL;
318+
}
319+
320+
return wrap_blame_hunk(hunk, self);
321+
}
322+
323+
static void
324+
Blame_dealloc(Blame *self)
325+
{
326+
git_blame_free(self->blame);
327+
Py_CLEAR(self->repo);
328+
PyObject_Del(self);
329+
}
330+
331+
PyMappingMethods Blame_as_mapping = {
332+
(lenfunc)Blame_len, /* mp_length */
333+
(binaryfunc)Blame_getitem, /* mp_subscript */
334+
0, /* mp_ass_subscript */
335+
};
336+
337+
static PyMethodDef Blame_methods[] = {
338+
METHOD(Blame, for_line, METH_VARARGS),
339+
{NULL}
340+
};
341+
342+
343+
PyDoc_STRVAR(Blame__doc__, "Blame objects.");
344+
345+
PyTypeObject BlameType = {
346+
PyVarObject_HEAD_INIT(NULL, 0)
347+
"_pygit2.Blame", /* tp_name */
348+
sizeof(Blame), /* tp_basicsize */
349+
0, /* tp_itemsize */
350+
(destructor)Blame_dealloc, /* tp_dealloc */
351+
0, /* tp_print */
352+
0, /* tp_getattr */
353+
0, /* tp_setattr */
354+
0, /* tp_compare */
355+
0, /* tp_repr */
356+
0, /* tp_as_number */
357+
0, /* tp_as_sequence */
358+
&Blame_as_mapping, /* tp_as_mapping */
359+
0, /* tp_hash */
360+
0, /* tp_call */
361+
0, /* tp_str */
362+
0, /* tp_getattro */
363+
0, /* tp_setattro */
364+
0, /* tp_as_buffer */
365+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
366+
Blame__doc__, /* tp_doc */
367+
0, /* tp_traverse */
368+
0, /* tp_clear */
369+
0, /* tp_richcompare */
370+
0, /* tp_weaklistoffset */
371+
(getiterfunc)Blame_iter, /* tp_iter */
372+
0, /* tp_iternext */
373+
Blame_methods, /* tp_methods */
374+
0, /* tp_members */
375+
0, /* tp_getset */
376+
0, /* tp_base */
377+
0, /* tp_dict */
378+
0, /* tp_descr_get */
379+
0, /* tp_descr_set */
380+
0, /* tp_dictoffset */
381+
0, /* tp_init */
382+
0, /* tp_alloc */
383+
0, /* tp_new */
384+
};

0 commit comments

Comments
 (0)