2 * Copyright (C) the libgit2 contributors. All rights reserved.
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
9 #include "git2/commit.h"
10 #include "git2/revparse.h"
11 #include "git2/revwalk.h"
12 #include "git2/tree.h"
13 #include "git2/diff.h"
14 #include "git2/blob.h"
15 #include "git2/signature.h"
17 #include "repository.h"
18 #include "blame_git.h"
21 static int hunk_byfinalline_search_cmp(const void *key
, const void *entry
)
23 git_blame_hunk
*hunk
= (git_blame_hunk
*)entry
;
25 size_t lineno
= *(size_t*)key
;
26 size_t lines_in_hunk
= (size_t)hunk
->lines_in_hunk
;
27 size_t final_start_line_number
= (size_t)hunk
->final_start_line_number
;
29 if (lineno
< final_start_line_number
)
31 if (lineno
>= final_start_line_number
+ lines_in_hunk
)
36 static int paths_cmp(const void *a
, const void *b
) { return git__strcmp((char*)a
, (char*)b
); }
37 static int hunk_cmp(const void *_a
, const void *_b
)
39 git_blame_hunk
*a
= (git_blame_hunk
*)_a
,
40 *b
= (git_blame_hunk
*)_b
;
42 return a
->final_start_line_number
- b
->final_start_line_number
;
45 static bool hunk_ends_at_or_before_line(git_blame_hunk
*hunk
, size_t line
)
47 return line
>= (size_t)(hunk
->final_start_line_number
+ hunk
->lines_in_hunk
- 1);
50 static bool hunk_starts_at_or_after_line(git_blame_hunk
*hunk
, size_t line
)
52 return line
<= hunk
->final_start_line_number
;
55 static git_blame_hunk
* new_hunk(
61 git_blame_hunk
*hunk
= git__calloc(1, sizeof(git_blame_hunk
));
62 if (!hunk
) return NULL
;
64 hunk
->lines_in_hunk
= lines
;
65 hunk
->final_start_line_number
= start
;
66 hunk
->orig_start_line_number
= orig_start
;
67 hunk
->orig_path
= path
? git__strdup(path
) : NULL
;
72 static git_blame_hunk
* dup_hunk(git_blame_hunk
*hunk
)
74 git_blame_hunk
*newhunk
= new_hunk(
75 hunk
->final_start_line_number
,
77 hunk
->orig_start_line_number
,
83 git_oid_cpy(&newhunk
->orig_commit_id
, &hunk
->orig_commit_id
);
84 git_oid_cpy(&newhunk
->final_commit_id
, &hunk
->final_commit_id
);
85 newhunk
->boundary
= hunk
->boundary
;
86 git_signature_dup(&newhunk
->final_signature
, hunk
->final_signature
);
87 git_signature_dup(&newhunk
->orig_signature
, hunk
->orig_signature
);
91 static void free_hunk(git_blame_hunk
*hunk
)
93 git__free((void*)hunk
->orig_path
);
94 git_signature_free(hunk
->final_signature
);
95 git_signature_free(hunk
->orig_signature
);
99 /* Starting with the hunk that includes start_line, shift all following hunks'
100 * final_start_line by shift_by lines */
101 static void shift_hunks_by(git_vector
*v
, size_t start_line
, int shift_by
)
105 if (!git_vector_bsearch2(&i
, v
, hunk_byfinalline_search_cmp
, &start_line
)) {
106 for (; i
< v
->length
; i
++) {
107 git_blame_hunk
*hunk
= (git_blame_hunk
*)v
->contents
[i
];
108 hunk
->final_start_line_number
+= shift_by
;
113 git_blame
* git_blame__alloc(
114 git_repository
*repo
,
115 git_blame_options opts
,
118 git_blame
*gbr
= git__calloc(1, sizeof(git_blame
));
122 gbr
->repository
= repo
;
125 if (git_vector_init(&gbr
->hunks
, 8, hunk_cmp
) < 0 ||
126 git_vector_init(&gbr
->paths
, 8, paths_cmp
) < 0 ||
127 (gbr
->path
= git__strdup(path
)) == NULL
||
128 git_vector_insert(&gbr
->paths
, git__strdup(path
)) < 0)
137 void git_blame_free(git_blame
*blame
)
140 git_blame_hunk
*hunk
;
144 git_vector_foreach(&blame
->hunks
, i
, hunk
)
146 git_vector_free(&blame
->hunks
);
148 git_vector_free_deep(&blame
->paths
);
150 git_array_clear(blame
->line_index
);
152 git__free(blame
->path
);
153 git_blob_free(blame
->final_blob
);
157 uint32_t git_blame_get_hunk_count(git_blame
*blame
)
160 return (uint32_t)blame
->hunks
.length
;
163 const git_blame_hunk
*git_blame_get_hunk_byindex(git_blame
*blame
, uint32_t index
)
166 return (git_blame_hunk
*)git_vector_get(&blame
->hunks
, index
);
169 const git_blame_hunk
*git_blame_get_hunk_byline(git_blame
*blame
, uint32_t lineno
)
171 size_t i
, new_lineno
= (size_t)lineno
;
174 if (!git_vector_bsearch2(&i
, &blame
->hunks
, hunk_byfinalline_search_cmp
, &new_lineno
)) {
175 return git_blame_get_hunk_byindex(blame
, (uint32_t)i
);
181 static void normalize_options(
182 git_blame_options
*out
,
183 const git_blame_options
*in
,
184 git_repository
*repo
)
186 git_blame_options dummy
= GIT_BLAME_OPTIONS_INIT
;
187 if (!in
) in
= &dummy
;
189 memcpy(out
, in
, sizeof(git_blame_options
));
191 /* No newest_commit => HEAD */
192 if (git_oid_iszero(&out
->newest_commit
)) {
193 git_reference_name_to_id(&out
->newest_commit
, repo
, "HEAD");
196 /* min_line 0 really means 1 */
197 if (!out
->min_line
) out
->min_line
= 1;
198 /* max_line 0 really means N, but we don't know N yet */
200 /* Fix up option implications */
201 if (out
->flags
& GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES
)
202 out
->flags
|= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES
;
203 if (out
->flags
& GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES
)
204 out
->flags
|= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES
;
205 if (out
->flags
& GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES
)
206 out
->flags
|= GIT_BLAME_TRACK_COPIES_SAME_FILE
;
209 static git_blame_hunk
*split_hunk_in_vector(
211 git_blame_hunk
*hunk
,
215 size_t new_line_count
;
218 /* Don't split if already at a boundary */
220 rel_line
>= hunk
->lines_in_hunk
)
225 new_line_count
= hunk
->lines_in_hunk
- rel_line
;
226 nh
= new_hunk((uint16_t)(hunk
->final_start_line_number
+rel_line
), (uint16_t)new_line_count
,
227 (uint16_t)(hunk
->orig_start_line_number
+rel_line
), hunk
->orig_path
);
232 git_oid_cpy(&nh
->final_commit_id
, &hunk
->final_commit_id
);
233 git_oid_cpy(&nh
->orig_commit_id
, &hunk
->orig_commit_id
);
235 /* Adjust hunk that was split */
236 hunk
->lines_in_hunk
-= (uint16_t)new_line_count
;
237 git_vector_insert_sorted(vec
, nh
, NULL
);
239 git_blame_hunk
*ret
= return_new
? nh
: hunk
;
245 * Construct a list of char indices for where lines begin
246 * Adapted from core git:
247 * https://github.com/gitster/git/blob/be5c9fb9049ed470e7005f159bb923a5f4de1309/builtin/blame.c#L1760-L1789
249 static int index_blob_lines(git_blame
*blame
)
251 const char *buf
= blame
->final_buf
;
252 git_off_t len
= blame
->final_buf_size
;
253 int num
= 0, incomplete
= 0, bol
= 1;
256 if (len
&& buf
[len
-1] != '\n')
257 incomplete
++; /* incomplete line at the end */
260 i
= git_array_alloc(blame
->line_index
);
261 GITERR_CHECK_ALLOC(i
);
262 *i
= buf
- blame
->final_buf
;
265 if (*buf
++ == '\n') {
270 i
= git_array_alloc(blame
->line_index
);
271 GITERR_CHECK_ALLOC(i
);
272 *i
= buf
- blame
->final_buf
;
273 blame
->num_lines
= num
+ incomplete
;
274 return blame
->num_lines
;
277 static git_blame_hunk
* hunk_from_entry(git_blame__entry
*e
)
279 git_blame_hunk
*h
= new_hunk(
280 e
->lno
+1, e
->num_lines
, e
->s_lno
+1, e
->suspect
->path
);
285 git_oid_cpy(&h
->final_commit_id
, git_commit_id(e
->suspect
->commit
));
286 git_oid_cpy(&h
->orig_commit_id
, git_commit_id(e
->suspect
->commit
));
287 git_signature_dup(&h
->final_signature
, git_commit_author(e
->suspect
->commit
));
288 git_signature_dup(&h
->orig_signature
, git_commit_author(e
->suspect
->commit
));
289 h
->boundary
= e
->is_boundary
? 1 : 0;
293 static int load_blob(git_blame
*blame
)
297 if (blame
->final_blob
) return 0;
299 error
= git_commit_lookup(&blame
->final
, blame
->repository
, &blame
->options
.newest_commit
);
302 error
= git_object_lookup_bypath((git_object
**)&blame
->final_blob
,
303 (git_object
*)blame
->final
, blame
->path
, GIT_OBJ_BLOB
);
309 static int blame_internal(git_blame
*blame
)
312 git_blame__entry
*ent
= NULL
;
313 git_blame__origin
*o
;
315 if ((error
= load_blob(blame
)) < 0 ||
316 (error
= git_blame__get_origin(&o
, blame
, blame
->final
, blame
->path
)) < 0)
318 blame
->final_buf
= git_blob_rawcontent(blame
->final_blob
);
319 blame
->final_buf_size
= git_blob_rawsize(blame
->final_blob
);
321 ent
= git__calloc(1, sizeof(git_blame__entry
));
322 GITERR_CHECK_ALLOC(ent
);
324 ent
->num_lines
= index_blob_lines(blame
);
325 ent
->lno
= blame
->options
.min_line
- 1;
326 ent
->num_lines
= ent
->num_lines
- blame
->options
.min_line
+ 1;
327 if (blame
->options
.max_line
> 0)
328 ent
->num_lines
= blame
->options
.max_line
- blame
->options
.min_line
+ 1;
329 ent
->s_lno
= ent
->lno
;
334 git_blame__like_git(blame
, blame
->options
.flags
);
337 for (ent
= blame
->ent
; ent
; ) {
338 git_blame__entry
*e
= ent
->next
;
339 git_blame_hunk
*h
= hunk_from_entry(ent
);
341 git_vector_insert(&blame
->hunks
, h
);
343 git_blame__free_entry(ent
);
350 /*******************************************************************************
352 ******************************************************************************/
356 git_repository
*repo
,
358 git_blame_options
*options
)
361 git_blame_options normOptions
= GIT_BLAME_OPTIONS_INIT
;
362 git_blame
*blame
= NULL
;
364 assert(out
&& repo
&& path
);
365 normalize_options(&normOptions
, options
, repo
);
367 blame
= git_blame__alloc(repo
, normOptions
, path
);
368 GITERR_CHECK_ALLOC(blame
);
370 if ((error
= load_blob(blame
)) < 0)
373 if ((error
= blame_internal(blame
)) < 0)
380 git_blame_free(blame
);
384 /*******************************************************************************
386 *******************************************************************************/
388 static bool hunk_is_bufferblame(git_blame_hunk
*hunk
)
390 return git_oid_iszero(&hunk
->final_commit_id
);
393 static int buffer_hunk_cb(
394 const git_diff_delta
*delta
,
395 const git_diff_hunk
*hunk
,
398 git_blame
*blame
= (git_blame
*)payload
;
403 wedge_line
= (hunk
->old_lines
== 0) ? hunk
->new_start
: hunk
->old_start
;
404 blame
->current_diff_line
= wedge_line
;
406 blame
->current_hunk
= (git_blame_hunk
*)git_blame_get_hunk_byline(blame
, wedge_line
);
407 if (!blame
->current_hunk
) {
408 /* Line added at the end of the file */
409 blame
->current_hunk
= new_hunk(wedge_line
, 0, wedge_line
, blame
->path
);
410 GITERR_CHECK_ALLOC(blame
->current_hunk
);
412 git_vector_insert(&blame
->hunks
, blame
->current_hunk
);
413 } else if (!hunk_starts_at_or_after_line(blame
->current_hunk
, wedge_line
)){
414 /* If this hunk doesn't start between existing hunks, split a hunk up so it does */
415 blame
->current_hunk
= split_hunk_in_vector(&blame
->hunks
, blame
->current_hunk
,
416 wedge_line
- blame
->current_hunk
->orig_start_line_number
, true);
417 GITERR_CHECK_ALLOC(blame
->current_hunk
);
423 static int ptrs_equal_cmp(const void *a
, const void *b
) { return a
<b
? -1 : a
>b
? 1 : 0; }
424 static int buffer_line_cb(
425 const git_diff_delta
*delta
,
426 const git_diff_hunk
*hunk
,
427 const git_diff_line
*line
,
430 git_blame
*blame
= (git_blame
*)payload
;
436 if (line
->origin
== GIT_DIFF_LINE_ADDITION
) {
437 if (hunk_is_bufferblame(blame
->current_hunk
) &&
438 hunk_ends_at_or_before_line(blame
->current_hunk
, blame
->current_diff_line
)) {
439 /* Append to the current buffer-blame hunk */
440 blame
->current_hunk
->lines_in_hunk
++;
441 shift_hunks_by(&blame
->hunks
, blame
->current_diff_line
+1, 1);
443 /* Create a new buffer-blame hunk with this line */
444 shift_hunks_by(&blame
->hunks
, blame
->current_diff_line
, 1);
445 blame
->current_hunk
= new_hunk((uint16_t)blame
->current_diff_line
, 1, 0, blame
->path
);
446 GITERR_CHECK_ALLOC(blame
->current_hunk
);
448 git_vector_insert_sorted(&blame
->hunks
, blame
->current_hunk
, NULL
);
450 blame
->current_diff_line
++;
453 if (line
->origin
== GIT_DIFF_LINE_DELETION
) {
454 /* Trim the line from the current hunk; remove it if it's now empty */
455 size_t shift_base
= blame
->current_diff_line
+ blame
->current_hunk
->lines_in_hunk
+1;
457 if (--(blame
->current_hunk
->lines_in_hunk
) == 0) {
460 if (!git_vector_search2(&i
, &blame
->hunks
, ptrs_equal_cmp
, blame
->current_hunk
)) {
461 git_vector_remove(&blame
->hunks
, i
);
462 free_hunk(blame
->current_hunk
);
463 blame
->current_hunk
= (git_blame_hunk
*)git_blame_get_hunk_byindex(blame
, (uint32_t)i
);
466 shift_hunks_by(&blame
->hunks
, shift_base
, -1);
471 int git_blame_buffer(
473 git_blame
*reference
,
478 git_diff_options diffopts
= GIT_DIFF_OPTIONS_INIT
;
480 git_blame_hunk
*hunk
;
482 diffopts
.context_lines
= 0;
484 assert(out
&& reference
&& buffer
&& buffer_len
);
486 blame
= git_blame__alloc(reference
->repository
, reference
->options
, reference
->path
);
487 GITERR_CHECK_ALLOC(blame
);
489 /* Duplicate all of the hunk structures in the reference blame */
490 git_vector_foreach(&reference
->hunks
, i
, hunk
) {
491 git_blame_hunk
*h
= dup_hunk(hunk
);
492 GITERR_CHECK_ALLOC(h
);
494 git_vector_insert(&blame
->hunks
, h
);
497 /* Diff to the reference blob */
498 git_diff_blob_to_buffer(reference
->final_blob
, blame
->path
,
499 buffer
, buffer_len
, blame
->path
, &diffopts
,
500 NULL
, NULL
, buffer_hunk_cb
, buffer_line_cb
, blame
);
506 int git_blame_init_options(git_blame_options
*opts
, unsigned int version
)
508 GIT_INIT_STRUCTURE_FROM_TEMPLATE(
509 opts
, version
, git_blame_options
, GIT_BLAME_OPTIONS_INIT
);