Skip to content

Commit 6653554

Browse files
committed
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.
1 parent a53d8b2 commit 6653554

File tree

7 files changed

+114
-475
lines changed

7 files changed

+114
-475
lines changed

pygit2/decl.h

+53-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ typedef ... git_push;
55
typedef ... git_cred;
66
typedef ... git_object;
77
typedef ... git_tree;
8-
typedef ... git_signature;
98
typedef ... git_index;
109
typedef ... git_diff;
1110
typedef ... git_index_conflict_iterator;
@@ -29,6 +28,7 @@ typedef struct git_strarray {
2928
} git_strarray;
3029

3130
typedef int64_t git_off_t;
31+
typedef int64_t git_time_t;
3232

3333
typedef enum {
3434
GIT_OK = 0,
@@ -55,6 +55,17 @@ typedef struct {
5555
int klass;
5656
} git_error;
5757

58+
typedef struct git_time {
59+
git_time_t time;
60+
int offset;
61+
} git_time;
62+
63+
typedef struct git_signature {
64+
char *name;
65+
char *email;
66+
git_time when;
67+
} git_signature;
68+
5869
const git_error * giterr_last(void);
5970

6071
void git_strarray_free(git_strarray *array);
@@ -506,3 +517,44 @@ int git_index_conflict_iterator_new(git_index_conflict_iterator **iterator_out,
506517
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);
507518
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);
508519
int git_index_conflict_remove(git_index *index, const char *path);
520+
521+
/*
522+
* git_blame
523+
*/
524+
525+
typedef ... git_blame;
526+
527+
typedef struct git_blame_options {
528+
unsigned int version;
529+
530+
uint32_t flags;
531+
uint16_t min_match_characters;
532+
git_oid newest_commit;
533+
git_oid oldest_commit;
534+
uint32_t min_line;
535+
uint32_t max_line;
536+
} git_blame_options;
537+
538+
#define GIT_BLAME_OPTIONS_VERSION ...
539+
540+
typedef struct git_blame_hunk {
541+
uint16_t lines_in_hunk;
542+
543+
git_oid final_commit_id;
544+
uint16_t final_start_line_number;
545+
git_signature *final_signature;
546+
547+
git_oid orig_commit_id;
548+
const char *orig_path;
549+
uint16_t orig_start_line_number;
550+
git_signature *orig_signature;
551+
552+
char boundary;
553+
} git_blame_hunk;
554+
555+
int git_blame_init_options(git_blame_options *opts, unsigned int version);
556+
uint32_t git_blame_get_hunk_count(git_blame *blame);
557+
const git_blame_hunk *git_blame_get_hunk_byindex(git_blame *blame, uint32_t index);
558+
const git_blame_hunk *git_blame_get_hunk_byline(git_blame *blame, uint32_t lineno);
559+
int git_blame_file(git_blame **out, git_repository *repo, const char *path, git_blame_options *options);
560+
void git_blame_free(git_blame *blame);

pygit2/repository.py

+57-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@
4242
from .ffi import ffi, C
4343
from .index import Index
4444
from .remote import Remote
45-
from .utils import to_bytes
45+
from .blame import Blame
46+
from .utils import to_bytes, to_str
4647

4748

4849
class Repository(_Repository):
@@ -370,6 +371,61 @@ def state_cleanup(self):
370371
"""
371372
C.git_repository_state_cleanup(self._repo)
372373

374+
#
375+
# blame
376+
#
377+
def blame(self, path, flags=None, min_match_characters=None, newest_commit=None, oldest_commit=None, min_line=None, max_line=None):
378+
"""blame(path, [flags, min_match_characters, newest_commit, oldest_commit,\n"
379+
min_line, max_line]) -> Blame
380+
381+
Get the blame for a single file.
382+
383+
Arguments:
384+
385+
path
386+
Path to the file to blame.
387+
flags
388+
A GIT_BLAME_* constant.
389+
min_match_characters
390+
The number of alphanum chars that must be detected as moving/copying
391+
within a file for it to associate those lines with the parent commit.
392+
newest_commit
393+
The id of the newest commit to consider.
394+
oldest_commit
395+
The id of the oldest commit to consider.
396+
min_line
397+
The first line in the file to blame.
398+
max_line
399+
The last line in the file to blame.
400+
401+
Examples::
402+
403+
repo.blame('foo.c', flags=GIT_BLAME_TRACK_COPIES_SAME_FILE)");
404+
"""
405+
406+
options = ffi.new('git_blame_options *')
407+
C.git_blame_init_options(options, C.GIT_BLAME_OPTIONS_VERSION)
408+
if min_match_characters:
409+
options.min_match_characters = min_match_characters
410+
if newest_commit:
411+
if not isinstance(newest_commit, Oid):
412+
newest_commit = Oid(hex=newest_commit)
413+
ffi.buffer(ffi.addressof(options, 'newest_commit'))[:] = newest_commit.raw
414+
if oldest_commit:
415+
if not isinstance(oldest_commit, Oid):
416+
oldest_commit = Oid(hex=oldest_commit)
417+
ffi.buffer(ffi.addressof(options, 'oldest_commit'))[:] = oldest_commit.raw
418+
if min_line:
419+
options.min_line = min_line
420+
if max_line:
421+
options.max_line = max_line
422+
423+
cblame = ffi.new('git_blame **')
424+
err = C.git_blame_file(cblame, self._repo, to_str(path), options)
425+
check_error(err)
426+
427+
return Blame._from_c(self, cblame[0])
428+
373429
#
374430
# Index
375431
#

0 commit comments

Comments
 (0)