mirror of
https://git.proxmox.com/git/libgit2
synced 2025-06-20 22:08:41 +00:00
Port blame from git.git
This commit is contained in:
parent
549931679a
commit
ceab4e2606
@ -36,6 +36,8 @@ typedef enum {
|
||||
/** Track lines that have been copied from another file that exists in *any*
|
||||
* commit (like `git blame -CCC`) */
|
||||
GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES = (1<<1 | 1<<2 | 1<<3),
|
||||
/** Track through file renames */
|
||||
GIT_BLAME_TRACK_FILE_RENAMES = (1<<4),
|
||||
} git_blame_flag_t;
|
||||
|
||||
/**
|
||||
@ -51,15 +53,14 @@ typedef enum {
|
||||
* associate those lines with the parent commit. The default value is 20.
|
||||
* This value only takes effect if any of the `GIT_BLAME_TRACK_COPIES_*`
|
||||
* flags are specified.
|
||||
* - `newest_commit` is a rev-parse spec that resolves to the most recent
|
||||
* commit to consider. The default is HEAD.
|
||||
* - `newest_commit` is the newest commit to consider. The default is HEAD.
|
||||
* - `oldest_commit` is the oldest commit to consider. The default is the
|
||||
* first commit encountered with a NULL parent.
|
||||
* - `newest_commit` is the id of the newest commit to consider. The default
|
||||
* is HEAD.
|
||||
* - `oldest_commit` is the id of the oldest commit to consider. The default
|
||||
* is the first commit encountered with a NULL parent.
|
||||
* - `min_line` is the first line in the file to blame. The default is 1 (line
|
||||
* numbers start with 1).
|
||||
* numbers start with 1).
|
||||
* - `max_line` is the last line in the file to blame. The default is the last
|
||||
* line of the file.
|
||||
* line of the file.
|
||||
*/
|
||||
|
||||
typedef struct git_blame_options {
|
||||
@ -67,8 +68,8 @@ typedef struct git_blame_options {
|
||||
|
||||
uint32_t flags;
|
||||
uint16_t min_match_characters;
|
||||
git_commit *newest_commit;
|
||||
git_commit *oldest_commit;
|
||||
git_oid newest_commit;
|
||||
git_oid oldest_commit;
|
||||
uint32_t min_line;
|
||||
uint32_t max_line;
|
||||
} git_blame_options;
|
||||
@ -105,39 +106,40 @@ typedef struct git_blame_hunk {
|
||||
} git_blame_hunk;
|
||||
|
||||
|
||||
typedef struct git_blame_results git_blame_results;
|
||||
/* Opaque structure to hold blame results */
|
||||
typedef struct git_blame git_blame;
|
||||
|
||||
/**
|
||||
* Gets the number of hunks that exist in the results structure.
|
||||
* Gets the number of hunks that exist in the blame structure.
|
||||
*/
|
||||
GIT_EXTERN(uint32_t) git_blame_results_hunk_count(git_blame_results *results);
|
||||
GIT_EXTERN(uint32_t) git_blame_get_hunk_count(git_blame *blame);
|
||||
|
||||
/**
|
||||
* Gets the blame hunk at the given index.
|
||||
*
|
||||
* @param results the results structure to query
|
||||
* @param blame the blame structure to query
|
||||
* @param index index of the hunk to retrieve
|
||||
* @return the hunk at the given index, or NULL on error
|
||||
*/
|
||||
GIT_EXTERN(const git_blame_hunk*) git_blame_results_hunk_byindex(
|
||||
git_blame_results *results,
|
||||
GIT_EXTERN(const git_blame_hunk*) git_blame_get_hunk_byindex(
|
||||
git_blame *blame,
|
||||
uint32_t index);
|
||||
|
||||
/**
|
||||
* Gets the hunk that relates to the given line number in the newest commit.
|
||||
*
|
||||
* @param results the results structure to query
|
||||
* @param blame the blame structure to query
|
||||
* @param lineno the (1-based) line number to find a hunk for
|
||||
* @return the hunk that contains the given line, or NULL on error
|
||||
*/
|
||||
GIT_EXTERN(const git_blame_hunk*) git_blame_results_hunk_byline(
|
||||
git_blame_results *results,
|
||||
GIT_EXTERN(const git_blame_hunk*) git_blame_get_hunk_byline(
|
||||
git_blame *blame,
|
||||
uint32_t lineno);
|
||||
|
||||
/**
|
||||
* Get the blame for a single file.
|
||||
*
|
||||
* @param out pointer that will receive the results object
|
||||
* @param out pointer that will receive the blame object
|
||||
* @param repo repository whose history is to be walked
|
||||
* @param path path to file to consider
|
||||
* @param options options for the blame operation. If NULL, this is treated as
|
||||
@ -146,32 +148,41 @@ GIT_EXTERN(const git_blame_hunk*) git_blame_results_hunk_byline(
|
||||
* about the error.)
|
||||
*/
|
||||
GIT_EXTERN(int) git_blame_file(
|
||||
git_blame_results **out,
|
||||
git_blame **out,
|
||||
git_repository *repo,
|
||||
const char *path,
|
||||
git_blame_options *options);
|
||||
|
||||
|
||||
/**
|
||||
* Get blame data for a file that has been modified.
|
||||
* Get blame data for a file that has been modified in memory. The `reference`
|
||||
* parameter is a pre-calculated blame for the in-odb history of the file. This
|
||||
* means that once a file blame is completed (which can be expensive), updating
|
||||
* the buffer blame is very fast.
|
||||
*
|
||||
* @param out pointer that will receive the results object
|
||||
* @param reference output from git_blame_file for the file in question
|
||||
* Lines that differ between the buffer and the committed version are marked as
|
||||
* having a zero OID for their final_commit_id.
|
||||
*
|
||||
* @param out pointer that will receive the resulting blame data
|
||||
* @param reference cached blame from the history of the file (usually the output
|
||||
* from git_blame_file)
|
||||
* @param buffer the (possibly) modified contents of the file
|
||||
* @param buffer_len number of valid bytes in the buffer
|
||||
* @return 0 on success, or an error code. (use giterr_last for information
|
||||
* about the error)
|
||||
*/
|
||||
GIT_EXTERN(int) git_blame_buffer(
|
||||
git_blame_results **out,
|
||||
git_blame_results *reference,
|
||||
const char *buffer);
|
||||
git_blame **out,
|
||||
git_blame *reference,
|
||||
const char *buffer,
|
||||
size_t buffer_len);
|
||||
|
||||
/**
|
||||
* Free memory allocated by git_blame.
|
||||
* Free memory allocated by git_blame_file or git_blame_buffer.
|
||||
*
|
||||
* @param results results structure to free
|
||||
* @param blame the blame structure to free
|
||||
*/
|
||||
GIT_EXTERN(void) git_blame_free(git_blame_results *results);
|
||||
GIT_EXTERN(void) git_blame_free(git_blame *blame);
|
||||
|
||||
/** @} */
|
||||
GIT_END_DECL
|
||||
|
@ -36,7 +36,7 @@ GIT_BEGIN_DECL
|
||||
* @param repo the repository to look up the object
|
||||
* @param id the unique identifier for the object
|
||||
* @param type the type of the object
|
||||
* @return 0 or an error code
|
||||
* @return a reference to the object
|
||||
*/
|
||||
GIT_EXTERN(int) git_object_lookup(
|
||||
git_object **object,
|
||||
@ -78,6 +78,23 @@ GIT_EXTERN(int) git_object_lookup_prefix(
|
||||
size_t len,
|
||||
git_otype type);
|
||||
|
||||
|
||||
/**
|
||||
* Lookup an object that represents a tree entry.
|
||||
*
|
||||
* @param out buffer that receives a pointer to the object (which must be freed
|
||||
* by the caller)
|
||||
* @param treeish root object that can be peeled to a tree
|
||||
* @param path relative path from the root object to the desired object
|
||||
* @param type type of object desired
|
||||
* @return 0 on success, or an error code
|
||||
*/
|
||||
GIT_EXTERN(int) git_object_lookup_bypath(
|
||||
git_object **out,
|
||||
const git_object *treeish,
|
||||
const char *path,
|
||||
git_otype type);
|
||||
|
||||
/**
|
||||
* Get the id (SHA1) of a repository object
|
||||
*
|
||||
|
492
src/blame.c
Normal file
492
src/blame.c
Normal file
@ -0,0 +1,492 @@
|
||||
/*
|
||||
* Copyright (C) the libgit2 contributors. All rights reserved.
|
||||
*
|
||||
* This file is part of libgit2, distributed under the GNU GPL v2 with
|
||||
* a Linking Exception. For full terms see the included COPYING file.
|
||||
*/
|
||||
|
||||
#include "blame.h"
|
||||
#include "git2/commit.h"
|
||||
#include "git2/revparse.h"
|
||||
#include "git2/revwalk.h"
|
||||
#include "git2/tree.h"
|
||||
#include "git2/diff.h"
|
||||
#include "git2/blob.h"
|
||||
#include "util.h"
|
||||
#include "repository.h"
|
||||
#include "blame_git.h"
|
||||
|
||||
|
||||
static int hunk_search_cmp_helper(const void *key, size_t start_line, size_t num_lines)
|
||||
{
|
||||
uint32_t lineno = *(size_t*)key;
|
||||
if (lineno < start_line)
|
||||
return -1;
|
||||
if (lineno >= ((uint32_t)start_line + num_lines))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
static int hunk_byfinalline_search_cmp(const void *key, const void *entry)
|
||||
{
|
||||
git_blame_hunk *hunk = (git_blame_hunk*)entry;
|
||||
return hunk_search_cmp_helper(key, hunk->final_start_line_number, hunk->lines_in_hunk);
|
||||
}
|
||||
static int hunk_sort_cmp_by_start_line(const void *_a, const void *_b)
|
||||
{
|
||||
git_blame_hunk *a = (git_blame_hunk*)_a,
|
||||
*b = (git_blame_hunk*)_b;
|
||||
|
||||
return a->final_start_line_number - b->final_start_line_number;
|
||||
}
|
||||
|
||||
static bool hunk_ends_at_or_before_line(git_blame_hunk *hunk, size_t line)
|
||||
{
|
||||
return line >= (size_t)(hunk->final_start_line_number + hunk->lines_in_hunk - 1);
|
||||
}
|
||||
|
||||
static bool hunk_starts_at_or_after_line(git_blame_hunk *hunk, size_t line)
|
||||
{
|
||||
return line <= hunk->final_start_line_number;
|
||||
}
|
||||
|
||||
static git_blame_hunk* new_hunk(uint16_t start, uint16_t lines, uint16_t orig_start, const char *path)
|
||||
{
|
||||
git_blame_hunk *hunk = git__calloc(1, sizeof(git_blame_hunk));
|
||||
if (!hunk) return NULL;
|
||||
|
||||
hunk->lines_in_hunk = lines;
|
||||
hunk->final_start_line_number = start;
|
||||
hunk->orig_start_line_number = orig_start;
|
||||
hunk->orig_path = path ? git__strdup(path) : NULL;
|
||||
|
||||
return hunk;
|
||||
}
|
||||
|
||||
git_blame_hunk* git_blame__alloc_hunk()
|
||||
{
|
||||
return new_hunk(0,0,0,NULL);
|
||||
}
|
||||
|
||||
static git_blame_hunk* dup_hunk(git_blame_hunk *hunk)
|
||||
{
|
||||
git_blame_hunk *newhunk = new_hunk(hunk->final_start_line_number, hunk->lines_in_hunk, hunk->orig_start_line_number, hunk->orig_path);
|
||||
git_oid_cpy(&newhunk->orig_commit_id, &hunk->orig_commit_id);
|
||||
git_oid_cpy(&newhunk->final_commit_id, &hunk->final_commit_id);
|
||||
return newhunk;
|
||||
}
|
||||
|
||||
static void free_hunk(git_blame_hunk *hunk)
|
||||
{
|
||||
git__free((void*)hunk->orig_path);
|
||||
git__free(hunk);
|
||||
}
|
||||
|
||||
/* Starting with the hunk that includes start_line, shift all following hunks'
|
||||
* final_start_line by shift_by lines */
|
||||
static void shift_hunks_by_final(git_vector *v, size_t start_line, int shift_by)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (!git_vector_bsearch2( &i, v, hunk_byfinalline_search_cmp, &start_line)) {
|
||||
for (; i < v->length; i++) {
|
||||
git_blame_hunk *hunk = (git_blame_hunk*)v->contents[i];
|
||||
hunk->final_start_line_number += shift_by;
|
||||
}
|
||||
}
|
||||
}
|
||||
static int paths_cmp(const void *a, const void *b) { return git__strcmp((char*)a, (char*)b); }
|
||||
git_blame* git_blame__alloc(
|
||||
git_repository *repo,
|
||||
git_blame_options opts,
|
||||
const char *path)
|
||||
{
|
||||
git_blame *gbr = (git_blame*)git__calloc(1, sizeof(git_blame));
|
||||
if (!gbr) {
|
||||
giterr_set_oom();
|
||||
return NULL;
|
||||
}
|
||||
git_vector_init(&gbr->hunks, 8, hunk_sort_cmp_by_start_line);
|
||||
git_vector_init(&gbr->unclaimed_hunks, 8, hunk_sort_cmp_by_start_line);
|
||||
git_vector_init(&gbr->paths, 8, paths_cmp);
|
||||
gbr->repository = repo;
|
||||
gbr->options = opts;
|
||||
gbr->path = git__strdup(path);
|
||||
git_vector_insert(&gbr->paths, git__strdup(path));
|
||||
gbr->final_blob = NULL;
|
||||
return gbr;
|
||||
}
|
||||
|
||||
void git_blame_free(git_blame *blame)
|
||||
{
|
||||
size_t i;
|
||||
git_blame_hunk *hunk;
|
||||
char *path;
|
||||
|
||||
if (!blame) return;
|
||||
|
||||
git_vector_foreach(&blame->hunks, i, hunk)
|
||||
free_hunk(hunk);
|
||||
git_vector_free(&blame->hunks);
|
||||
|
||||
git_vector_foreach(&blame->unclaimed_hunks, i, hunk)
|
||||
free_hunk(hunk);
|
||||
git_vector_free(&blame->unclaimed_hunks);
|
||||
|
||||
git_vector_foreach(&blame->paths, i, path)
|
||||
git__free(path);
|
||||
git_vector_free(&blame->paths);
|
||||
|
||||
git__free((void*)blame->path);
|
||||
git_blob_free(blame->final_blob);
|
||||
git__free(blame);
|
||||
}
|
||||
|
||||
uint32_t git_blame_get_hunk_count(git_blame *blame)
|
||||
{
|
||||
assert(blame);
|
||||
return blame->hunks.length;
|
||||
}
|
||||
|
||||
const git_blame_hunk *git_blame_get_hunk_byindex(git_blame *blame, uint32_t index)
|
||||
{
|
||||
assert(blame);
|
||||
return (git_blame_hunk*)git_vector_get(&blame->hunks, index);
|
||||
}
|
||||
|
||||
const git_blame_hunk *git_blame_get_hunk_byline(git_blame *blame, uint32_t lineno)
|
||||
{
|
||||
size_t i;
|
||||
assert(blame);
|
||||
|
||||
if (!git_vector_bsearch2( &i, &blame->hunks, hunk_byfinalline_search_cmp, &lineno)) {
|
||||
return git_blame_get_hunk_byindex(blame, i);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void normalize_options(
|
||||
git_blame_options *out,
|
||||
const git_blame_options *in,
|
||||
git_repository *repo)
|
||||
{
|
||||
git_blame_options dummy = GIT_BLAME_OPTIONS_INIT;
|
||||
if (!in) in = &dummy;
|
||||
|
||||
memcpy(out, in, sizeof(git_blame_options));
|
||||
|
||||
/* No newest_commit => HEAD */
|
||||
if (git_oid_iszero(&out->newest_commit)) {
|
||||
git_reference_name_to_id(&out->newest_commit, repo, "HEAD");
|
||||
}
|
||||
}
|
||||
|
||||
static git_blame_hunk *split_hunk_in_vector(
|
||||
git_vector *vec,
|
||||
git_blame_hunk *hunk,
|
||||
size_t rel_line,
|
||||
bool return_new)
|
||||
{
|
||||
size_t new_line_count;
|
||||
git_blame_hunk *nh;
|
||||
|
||||
/* Don't split if already at a boundary */
|
||||
if (rel_line <= 0 ||
|
||||
rel_line >= hunk->lines_in_hunk)
|
||||
{
|
||||
return hunk;
|
||||
}
|
||||
|
||||
new_line_count = hunk->lines_in_hunk - rel_line;
|
||||
nh = new_hunk(hunk->final_start_line_number+rel_line, new_line_count,
|
||||
hunk->orig_start_line_number+rel_line, hunk->orig_path);
|
||||
git_oid_cpy(&nh->final_commit_id, &hunk->final_commit_id);
|
||||
git_oid_cpy(&nh->orig_commit_id, &hunk->orig_commit_id);
|
||||
|
||||
/* Adjust hunk that was split */
|
||||
hunk->lines_in_hunk -= new_line_count;
|
||||
git_vector_insert_sorted(vec, nh, NULL);
|
||||
{
|
||||
git_blame_hunk *ret = return_new ? nh : hunk;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* To allow quick access to the contents of nth line in the
|
||||
* final image, prepare an index in the scoreboard.
|
||||
*/
|
||||
static int prepare_lines(struct scoreboard *sb)
|
||||
{
|
||||
const char *buf = sb->final_buf;
|
||||
git_off_t len = sb->final_buf_size;
|
||||
int num = 0, incomplete = 0, bol = 1;
|
||||
|
||||
if (len && buf[len-1] != '\n')
|
||||
incomplete++; /* incomplete line at the end */
|
||||
while (len--) {
|
||||
if (bol) {
|
||||
bol = 0;
|
||||
}
|
||||
if (*buf++ == '\n') {
|
||||
num++;
|
||||
bol = 1;
|
||||
}
|
||||
}
|
||||
sb->num_lines = num + incomplete;
|
||||
return sb->num_lines;
|
||||
}
|
||||
|
||||
static git_blame_hunk* hunk_from_entry(struct blame_entry *e)
|
||||
{
|
||||
git_blame_hunk *h = new_hunk(
|
||||
e->lno+1, e->num_lines, e->s_lno+1, e->suspect->path);
|
||||
git_oid_cpy(&h->final_commit_id, git_commit_id(e->suspect->commit));
|
||||
return h;
|
||||
}
|
||||
|
||||
static void free_if_not_already_freed(git_vector *already, struct origin *o)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (!o) return;
|
||||
if (!git_vector_search(&i, already, o))
|
||||
return;
|
||||
|
||||
git_vector_insert(already, o);
|
||||
free_if_not_already_freed(already, o->previous);
|
||||
git_blob_free(o->blob);
|
||||
git_commit_free(o->commit);
|
||||
git__free(o);
|
||||
}
|
||||
|
||||
static int walk_and_mark(git_blame *blame)
|
||||
{
|
||||
int error;
|
||||
|
||||
struct scoreboard sb = {0};
|
||||
struct blame_entry *ent = NULL;
|
||||
git_blob *blob = NULL;
|
||||
struct origin *o;
|
||||
git_vector already = GIT_VECTOR_INIT;
|
||||
|
||||
if ((error = git_commit_lookup(&sb.final, blame->repository, &blame->options.newest_commit)) < 0 ||
|
||||
(error = git_object_lookup_bypath((git_object**)&blob, (git_object*)sb.final, blame->path, GIT_OBJ_BLOB)) < 0)
|
||||
goto cleanup;
|
||||
sb.final_buf = git_blob_rawcontent(blob);
|
||||
sb.final_buf_size = git_blob_rawsize(blob);
|
||||
if ((error = get_origin(&o, &sb, sb.final, blame->path)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
ent = git__calloc(1, sizeof(*ent));
|
||||
ent->num_lines = prepare_lines(&sb);
|
||||
ent->suspect = o;
|
||||
sb.ent = ent;
|
||||
sb.path = blame->path;
|
||||
sb.blame = blame;
|
||||
|
||||
assign_blame(&sb, blame->options.flags);
|
||||
coalesce(&sb);
|
||||
|
||||
for (ent = sb.ent; ent; ) {
|
||||
git_vector_insert(&blame->hunks, hunk_from_entry(ent));
|
||||
ent = ent->next;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
for (ent = sb.ent; ent; ) {
|
||||
struct blame_entry *e = ent->next;
|
||||
struct origin *o = ent->suspect;
|
||||
|
||||
/* Linkages might not be ordered, so we only free pointers we haven't
|
||||
* seen before. */
|
||||
free_if_not_already_freed(&already, o);
|
||||
|
||||
git__free(ent);
|
||||
ent = e;
|
||||
}
|
||||
|
||||
git_vector_free(&already);
|
||||
git_commit_free(sb.final);
|
||||
git_blob_free(blob);
|
||||
return error;
|
||||
}
|
||||
|
||||
static int load_blob(git_blame *blame, git_repository *repo, git_oid *commit_id, const char *path)
|
||||
{
|
||||
int retval = -1;
|
||||
git_commit *commit = NULL;
|
||||
git_tree *tree = NULL;
|
||||
git_tree_entry *tree_entry = NULL;
|
||||
git_object *obj = NULL;
|
||||
|
||||
if (((retval = git_commit_lookup(&commit, repo, commit_id)) < 0) ||
|
||||
((retval = git_object_lookup_bypath(&obj, (git_object*)commit, path, GIT_OBJ_BLOB)) < 0) ||
|
||||
((retval = git_object_type(obj)) != GIT_OBJ_BLOB))
|
||||
goto cleanup;
|
||||
blame->final_blob = (git_blob*)obj;
|
||||
|
||||
cleanup:
|
||||
git_tree_entry_free(tree_entry);
|
||||
git_tree_free(tree);
|
||||
git_commit_free(commit);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* File blaming
|
||||
******************************************************************************/
|
||||
|
||||
int git_blame_file(
|
||||
git_blame **out,
|
||||
git_repository *repo,
|
||||
const char *path,
|
||||
git_blame_options *options)
|
||||
{
|
||||
int error = -1;
|
||||
git_blame_options normOptions = GIT_BLAME_OPTIONS_INIT;
|
||||
git_blame *blame = NULL;
|
||||
|
||||
assert(out && repo && path);
|
||||
normalize_options(&normOptions, options, repo);
|
||||
|
||||
blame = git_blame__alloc(repo, normOptions, path);
|
||||
GITERR_CHECK_ALLOC(blame);
|
||||
|
||||
if ((error = load_blob(blame, repo, &normOptions.newest_commit, path)) < 0)
|
||||
goto on_error;
|
||||
|
||||
if ((error = walk_and_mark(blame)) < 0)
|
||||
goto on_error;
|
||||
|
||||
*out = blame;
|
||||
return 0;
|
||||
|
||||
on_error:
|
||||
git_blame_free(blame);
|
||||
return error;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Buffer blaming
|
||||
*******************************************************************************/
|
||||
|
||||
static bool hunk_is_bufferblame(git_blame_hunk *hunk)
|
||||
{
|
||||
return git_oid_iszero(&hunk->final_commit_id);
|
||||
}
|
||||
|
||||
static int buffer_hunk_cb(
|
||||
const git_diff_delta *delta,
|
||||
const git_diff_range *range,
|
||||
const char *header,
|
||||
size_t header_len,
|
||||
void *payload)
|
||||
{
|
||||
git_blame *blame = (git_blame*)payload;
|
||||
size_t wedge_line;
|
||||
|
||||
GIT_UNUSED(delta);
|
||||
GIT_UNUSED(header);
|
||||
GIT_UNUSED(header_len);
|
||||
|
||||
wedge_line = (range->old_lines == 0) ? range->new_start : range->old_start;
|
||||
blame->current_diff_line = wedge_line;
|
||||
|
||||
/* If this hunk doesn't start between existing hunks, split a hunk up so it does */
|
||||
blame->current_hunk = (git_blame_hunk*)git_blame_get_hunk_byline(blame, wedge_line);
|
||||
if (!hunk_starts_at_or_after_line(blame->current_hunk, wedge_line)){
|
||||
blame->current_hunk = split_hunk_in_vector(&blame->hunks, blame->current_hunk,
|
||||
wedge_line - blame->current_hunk->orig_start_line_number, true);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ptrs_equal_cmp(const void *a, const void *b) { return a<b ? -1 : a>b ? 1 : 0; }
|
||||
static int buffer_line_cb(
|
||||
const git_diff_delta *delta,
|
||||
const git_diff_range *range,
|
||||
char line_origin,
|
||||
const char *content,
|
||||
size_t content_len,
|
||||
void *payload)
|
||||
{
|
||||
git_blame *blame = (git_blame*)payload;
|
||||
|
||||
GIT_UNUSED(delta);
|
||||
GIT_UNUSED(range);
|
||||
GIT_UNUSED(content);
|
||||
GIT_UNUSED(content_len);
|
||||
|
||||
#ifdef DO_DEBUG
|
||||
{
|
||||
char *str = git__substrdup(content, content_len);
|
||||
git__free(str);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (line_origin == GIT_DIFF_LINE_ADDITION) {
|
||||
if (hunk_is_bufferblame(blame->current_hunk) &&
|
||||
hunk_ends_at_or_before_line(blame->current_hunk, blame->current_diff_line)) {
|
||||
/* Append to the current buffer-blame hunk */
|
||||
blame->current_hunk->lines_in_hunk++;
|
||||
shift_hunks_by_final(&blame->hunks, blame->current_diff_line+1, 1);
|
||||
} else {
|
||||
/* Create a new buffer-blame hunk with this line */
|
||||
shift_hunks_by_final(&blame->hunks, blame->current_diff_line, 1);
|
||||
blame->current_hunk = new_hunk(blame->current_diff_line, 1, 0, blame->path);
|
||||
git_vector_insert_sorted(&blame->hunks, blame->current_hunk, NULL);
|
||||
}
|
||||
blame->current_diff_line++;
|
||||
}
|
||||
|
||||
if (line_origin == GIT_DIFF_LINE_DELETION) {
|
||||
/* Trim the line from the current hunk; remove it if it's now empty */
|
||||
size_t shift_base = blame->current_diff_line + blame->current_hunk->lines_in_hunk+1;
|
||||
|
||||
if (--(blame->current_hunk->lines_in_hunk) == 0) {
|
||||
size_t i;
|
||||
shift_base--;
|
||||
if (!git_vector_search2(&i, &blame->hunks, ptrs_equal_cmp, blame->current_hunk)) {
|
||||
git_vector_remove(&blame->hunks, i);
|
||||
free_hunk(blame->current_hunk);
|
||||
blame->current_hunk = (git_blame_hunk*)git_blame_get_hunk_byindex(blame, i);
|
||||
}
|
||||
}
|
||||
shift_hunks_by_final(&blame->hunks, shift_base, -1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int git_blame_buffer(
|
||||
git_blame **out,
|
||||
git_blame *reference,
|
||||
const char *buffer,
|
||||
size_t buffer_len)
|
||||
{
|
||||
git_blame *blame;
|
||||
git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
|
||||
size_t i;
|
||||
git_blame_hunk *hunk;
|
||||
|
||||
diffopts.context_lines = 0;
|
||||
|
||||
assert(out && reference && buffer && buffer_len);
|
||||
|
||||
blame = git_blame__alloc(reference->repository, reference->options, reference->path);
|
||||
|
||||
/* Duplicate all of the hunk structures in the reference blame */
|
||||
git_vector_foreach(&reference->hunks, i, hunk) {
|
||||
git_vector_insert(&blame->hunks, dup_hunk(hunk));
|
||||
}
|
||||
|
||||
/* Diff to the reference blob */
|
||||
git_diff_blob_to_buffer(reference->final_blob, blame->path,
|
||||
buffer, buffer_len, blame->path,
|
||||
&diffopts, NULL, buffer_hunk_cb, buffer_line_cb, blame);
|
||||
|
||||
*out = blame;
|
||||
return 0;
|
||||
}
|
37
src/blame.h
Normal file
37
src/blame.h
Normal file
@ -0,0 +1,37 @@
|
||||
#ifndef INCLUDE_blame_h__
|
||||
#define INCLUDE_blame_h__
|
||||
|
||||
#include "git2/blame.h"
|
||||
#include "common.h"
|
||||
#include "vector.h"
|
||||
#include "diff.h"
|
||||
#include "array.h"
|
||||
#include "git2/oid.h"
|
||||
|
||||
struct git_blame {
|
||||
const char *path;
|
||||
git_repository *repository;
|
||||
git_blame_options options;
|
||||
|
||||
git_vector hunks;
|
||||
git_vector unclaimed_hunks;
|
||||
git_vector paths;
|
||||
|
||||
git_blob *final_blob;
|
||||
size_t num_lines;
|
||||
|
||||
git_oid current_commit;
|
||||
git_oid parent_commit;
|
||||
size_t current_diff_line;
|
||||
size_t current_blame_line;
|
||||
git_blame_hunk *current_hunk;
|
||||
};
|
||||
|
||||
git_blame *git_blame__alloc(
|
||||
git_repository *repo,
|
||||
git_blame_options opts,
|
||||
const char *path);
|
||||
|
||||
git_blame_hunk *git_blame__alloc_hunk();
|
||||
|
||||
#endif
|
591
src/blame_git.c
Normal file
591
src/blame_git.c
Normal file
@ -0,0 +1,591 @@
|
||||
/*
|
||||
* Copyright (C) the libgit2 contributors. All rights reserved.
|
||||
*
|
||||
* This file is part of libgit2, distributed under the GNU GPL v2 with
|
||||
* a Linking Exception. For full terms see the included COPYING file.
|
||||
*/
|
||||
|
||||
#include "blame_git.h"
|
||||
#include "commit.h"
|
||||
|
||||
/*
|
||||
* Locate an existing origin or create a new one.
|
||||
*/
|
||||
int get_origin(struct origin **out, struct scoreboard *sb, git_commit *commit, const char *path)
|
||||
{
|
||||
struct blame_entry *e;
|
||||
|
||||
for (e = sb->ent; e; e = e->next)
|
||||
if (e->suspect->commit == commit && !strcmp(e->suspect->path, path))
|
||||
*out = origin_incref(e->suspect);
|
||||
return make_origin(out, commit, path);
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a commit and a path in it, create a new origin structure.
|
||||
* The callers that add blame to the scoreboard should use
|
||||
* get_origin() to obtain shared, refcounted copy instead of calling
|
||||
* this function directly.
|
||||
*/
|
||||
int make_origin(struct origin **out, git_commit *commit, const char *path)
|
||||
{
|
||||
int error = 0;
|
||||
struct origin *o;
|
||||
o = git__calloc(1, sizeof(*o) + strlen(path) + 1);
|
||||
GITERR_CHECK_ALLOC(o);
|
||||
o->commit = commit;
|
||||
o->refcnt = 1;
|
||||
strcpy(o->path, path);
|
||||
|
||||
if (!(error = git_object_lookup_bypath((git_object**)&o->blob, (git_object*)commit,
|
||||
path, GIT_OBJ_BLOB)))
|
||||
*out = o;
|
||||
else
|
||||
origin_decref(o);
|
||||
return error;
|
||||
}
|
||||
|
||||
struct blame_chunk_cb_data {
|
||||
struct scoreboard *sb;
|
||||
struct origin *target;
|
||||
struct origin *parent;
|
||||
long plno;
|
||||
long tlno;
|
||||
};
|
||||
|
||||
static bool same_suspect(struct origin *a, struct origin *b)
|
||||
{
|
||||
if (a == b)
|
||||
return true;
|
||||
if (git_oid_cmp(git_commit_id(a->commit), git_commit_id(b->commit)))
|
||||
return false;
|
||||
return 0 == strcmp(a->path, b->path);
|
||||
}
|
||||
|
||||
/* Find the line number of the last line the target is suspected for */
|
||||
static int find_last_in_target(struct scoreboard *sb, struct origin *target)
|
||||
{
|
||||
struct blame_entry *e;
|
||||
int last_in_target = -1;
|
||||
|
||||
for (e=sb->ent; e; e=e->next) {
|
||||
if (e->guilty || !same_suspect(e->suspect, target))
|
||||
continue;
|
||||
if (last_in_target < e->s_lno + e->num_lines)
|
||||
last_in_target = e->s_lno + e->num_lines;
|
||||
}
|
||||
return last_in_target;
|
||||
}
|
||||
|
||||
/*
|
||||
* It is known that lines between tlno to same came from parent, and e
|
||||
* has an overlap with that range. it also is known that parent's
|
||||
* line plno corresponds to e's line tlno.
|
||||
*
|
||||
* <---- e ----->
|
||||
* <------>
|
||||
* <------------>
|
||||
* <------------>
|
||||
* <------------------>
|
||||
*
|
||||
* Split e into potentially three parts; before this chunk, the chunk
|
||||
* to be blamed for the parent, and after that portion.
|
||||
*/
|
||||
static void split_overlap(struct blame_entry *split, struct blame_entry *e,
|
||||
int tlno, int plno, int same, struct origin *parent)
|
||||
{
|
||||
int chunk_end_lno;
|
||||
|
||||
if (e->s_lno < tlno) {
|
||||
/* there is a pre-chunk part not blamed on the parent */
|
||||
split[0].suspect = origin_incref(e->suspect);
|
||||
split[0].lno = e->lno;
|
||||
split[0].s_lno = e->s_lno;
|
||||
split[0].num_lines = tlno - e->s_lno;
|
||||
split[1].lno = e->lno + tlno - e->s_lno;
|
||||
split[1].s_lno = plno;
|
||||
} else {
|
||||
split[1].lno = e->lno;
|
||||
split[1].s_lno = plno + (e->s_lno - tlno);
|
||||
}
|
||||
|
||||
if (same < e->s_lno + e->num_lines) {
|
||||
/* there is a post-chunk part not blamed on parent */
|
||||
split[2].suspect = origin_incref(e->suspect);
|
||||
split[2].lno = e->lno + (same - e->s_lno);
|
||||
split[2].s_lno = e->s_lno + (same - e->s_lno);
|
||||
split[2].num_lines = e->s_lno + e->num_lines - same;
|
||||
chunk_end_lno = split[2].lno;
|
||||
} else {
|
||||
chunk_end_lno = e->lno + e->num_lines;
|
||||
}
|
||||
split[1].num_lines = chunk_end_lno - split[1].lno;
|
||||
|
||||
/*
|
||||
* if it turns out there is nothing to blame the parent for, forget about
|
||||
* the splitting. !split[1].suspect signals this.
|
||||
*/
|
||||
if (split[1].num_lines < 1)
|
||||
return;
|
||||
split[1].suspect = origin_incref(parent);
|
||||
}
|
||||
|
||||
/*
|
||||
* Link in a new blame entry to the scoreboard. Entries that cover the same
|
||||
* line range have been removed from the scoreboard previously.
|
||||
*/
|
||||
static void add_blame_entry(struct scoreboard *sb, struct blame_entry *e)
|
||||
{
|
||||
struct blame_entry *ent, *prev = NULL;
|
||||
|
||||
origin_incref(e->suspect);
|
||||
|
||||
for (ent = sb->ent; ent && ent->lno < e->lno; ent = ent->next)
|
||||
prev = ent;
|
||||
|
||||
/* prev, if not NULL, is the last one that is below e */
|
||||
e->prev = prev;
|
||||
if (prev) {
|
||||
e->next = prev->next;
|
||||
prev->next = e;
|
||||
} else {
|
||||
e->next = sb->ent;
|
||||
sb->ent = e;
|
||||
}
|
||||
if (e->next)
|
||||
e->next->prev = e;
|
||||
}
|
||||
|
||||
/*
|
||||
* src typically is on-stack; we want to copy the information in it to
|
||||
* a malloced blame_entry that is already on the linked list of the scoreboard.
|
||||
* The origin of dst loses a refcnt while the origin of src gains one.
|
||||
*/
|
||||
static void dup_entry(struct blame_entry *dst, struct blame_entry *src)
|
||||
{
|
||||
struct blame_entry *p, *n;
|
||||
|
||||
p = dst->prev;
|
||||
n = dst->next;
|
||||
origin_incref(src->suspect);
|
||||
origin_decref(dst->suspect);
|
||||
memcpy(dst, src, sizeof(*src));
|
||||
dst->prev = p;
|
||||
dst->next = n;
|
||||
dst->score = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* split_overlap() divided an existing blame e into up to three parts in split.
|
||||
* Adjust the linked list of blames in the scoreboard to reflect the split.
|
||||
*/
|
||||
static void split_blame(struct scoreboard *sb, struct blame_entry *split, struct blame_entry *e)
|
||||
{
|
||||
struct blame_entry *new_entry;
|
||||
|
||||
if (split[0].suspect && split[2].suspect) {
|
||||
/* The first part (reuse storage for the existing entry e */
|
||||
dup_entry(e, &split[0]);
|
||||
|
||||
/* The last part -- me */
|
||||
new_entry = git__malloc(sizeof(*new_entry));
|
||||
memcpy(new_entry, &(split[2]), sizeof(struct blame_entry));
|
||||
add_blame_entry(sb, new_entry);
|
||||
|
||||
/* ... and the middle part -- parent */
|
||||
new_entry = git__malloc(sizeof(*new_entry));
|
||||
memcpy(new_entry, &(split[1]), sizeof(struct blame_entry));
|
||||
add_blame_entry(sb, new_entry);
|
||||
} else if (!split[0].suspect && !split[2].suspect) {
|
||||
/*
|
||||
* The parent covers the entire area; reuse storage for e and replace it
|
||||
* with the parent
|
||||
*/
|
||||
dup_entry(e, &split[1]);
|
||||
} else if (split[0].suspect) {
|
||||
/* me and then parent */
|
||||
dup_entry(e, &split[0]);
|
||||
new_entry = git__malloc(sizeof(*new_entry));
|
||||
memcpy(new_entry, &(split[1]), sizeof(struct blame_entry));
|
||||
add_blame_entry(sb, new_entry);
|
||||
} else {
|
||||
/* parent and then me */
|
||||
dup_entry(e, &split[1]);
|
||||
new_entry = git__malloc(sizeof(*new_entry));
|
||||
memcpy(new_entry, &(split[2]), sizeof(struct blame_entry));
|
||||
add_blame_entry(sb, new_entry);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* After splitting the blame, the origins used by the on-stack blame_entry
|
||||
* should lose one refcnt each.
|
||||
*/
|
||||
static void decref_split(struct blame_entry *split)
|
||||
{
|
||||
int i;
|
||||
for (i=0; i<3; i++)
|
||||
origin_decref(split[i].suspect);
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper for blame_chunk(). blame_entry e is known to overlap with the patch
|
||||
* hunk; split it and pass blame to the parent.
|
||||
*/
|
||||
static void blame_overlap(struct scoreboard *sb, struct blame_entry *e, int tlno, int plno, int same, struct origin *parent)
|
||||
{
|
||||
struct blame_entry split[3] = {{0}};
|
||||
|
||||
split_overlap(split, e, tlno, plno, same, parent);
|
||||
if (split[1].suspect)
|
||||
split_blame(sb, split, e);
|
||||
decref_split(split);
|
||||
}
|
||||
|
||||
/*
|
||||
* Process one hunk from the patch between the current suspect for blame_entry
|
||||
* e and its parent. Find and split the overlap, and pass blame to the
|
||||
* overlapping part to the parent.
|
||||
*/
|
||||
static void blame_chunk(struct scoreboard *sb, int tlno, int plno, int same, struct origin *target, struct origin *parent)
|
||||
{
|
||||
struct blame_entry *e;
|
||||
|
||||
for (e = sb->ent; e; e = e->next) {
|
||||
if (e->guilty || !same_suspect(e->suspect, target))
|
||||
continue;
|
||||
if (same <= e->s_lno)
|
||||
continue;
|
||||
if (tlno < e->s_lno + e->num_lines) {
|
||||
blame_overlap(sb, e, tlno, plno, same, parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void blame_chunk_cb(long start_a, long count_a, long start_b, long count_b, void *data)
|
||||
{
|
||||
struct blame_chunk_cb_data *d = data;
|
||||
blame_chunk(d->sb, d->tlno, d->plno, start_b, d->target, d->parent);
|
||||
d->plno = start_a + count_a;
|
||||
d->tlno = start_b + count_b;
|
||||
}
|
||||
|
||||
static int my_emit_func(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, xdemitconf_t const *xecfg)
|
||||
{
|
||||
xdchange_t *xch = xscr;
|
||||
GIT_UNUSED(xe);
|
||||
GIT_UNUSED(xecfg);
|
||||
while (xch) {
|
||||
blame_chunk_cb(xch->i1, xch->chg1, xch->i2, xch->chg2, ecb->priv);
|
||||
xch = xch->next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void trim_common_tail(mmfile_t *a, mmfile_t *b, long ctx)
|
||||
{
|
||||
const int blk = 1024;
|
||||
long trimmed = 0, recovered = 0;
|
||||
char *ap = a->ptr + a->size;
|
||||
char *bp = b->ptr + b->size;
|
||||
long smaller = (a->size < b->size) ? a->size : b->size;
|
||||
|
||||
if (ctx)
|
||||
return;
|
||||
|
||||
while (blk + trimmed <= smaller && !memcmp(ap - blk, bp - blk, blk)) {
|
||||
trimmed += blk;
|
||||
ap -= blk;
|
||||
bp -= blk;
|
||||
}
|
||||
|
||||
while (recovered < trimmed)
|
||||
if (ap[recovered++] == '\n')
|
||||
break;
|
||||
a->size -= trimmed - recovered;
|
||||
b->size -= trimmed - recovered;
|
||||
}
|
||||
|
||||
static int xdi_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdemitconf_t const *xecfg, xdemitcb_t *xecb)
|
||||
{
|
||||
mmfile_t a = *mf1;
|
||||
mmfile_t b = *mf2;
|
||||
|
||||
trim_common_tail(&a, &b, xecfg->ctxlen);
|
||||
|
||||
return xdl_diff(&a, &b, xpp, xecfg, xecb);
|
||||
}
|
||||
|
||||
|
||||
static int diff_hunks(mmfile_t *file_a, mmfile_t *file_b, void *cb_data)
|
||||
{
|
||||
xpparam_t xpp = {0};
|
||||
xdemitconf_t xecfg = {0};
|
||||
xdemitcb_t ecb = {0};
|
||||
|
||||
xecfg.emit_func = (void(*)(void))my_emit_func;
|
||||
ecb.priv = cb_data;
|
||||
return xdi_diff(file_a, file_b, &xpp, &xecfg, &ecb);
|
||||
}
|
||||
|
||||
static void fill_origin_blob(struct origin *o, mmfile_t *file)
|
||||
{
|
||||
memset(file, 0, sizeof(*file));
|
||||
if (o->blob) {
|
||||
file->ptr = (char*)git_blob_rawcontent(o->blob);
|
||||
file->size = (size_t)git_blob_rawsize(o->blob);
|
||||
}
|
||||
}
|
||||
|
||||
static int pass_blame_to_parent(struct scoreboard *sb,
|
||||
struct origin *target,
|
||||
struct origin *parent)
|
||||
{
|
||||
int last_in_target;
|
||||
mmfile_t file_p, file_o;
|
||||
struct blame_chunk_cb_data d = { sb, target, parent, 0, 0 };
|
||||
|
||||
last_in_target = find_last_in_target(sb, target);
|
||||
if (last_in_target < 0)
|
||||
return 1; /* nothing remains for this target */
|
||||
|
||||
fill_origin_blob(parent, &file_p);
|
||||
fill_origin_blob(target, &file_o);
|
||||
|
||||
diff_hunks(&file_p, &file_o, &d);
|
||||
/* The reset (i.e. anything after tlno) are the same as the parent */
|
||||
blame_chunk(sb, d.tlno, d.plno, last_in_target, target, parent);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int paths_on_dup(void **old, void *new)
|
||||
{
|
||||
GIT_UNUSED(old);
|
||||
git__free(new);
|
||||
return -1;
|
||||
}
|
||||
static struct origin* find_origin(struct scoreboard *sb, git_commit *parent,
|
||||
struct origin *origin)
|
||||
{
|
||||
struct origin *porigin = NULL;
|
||||
git_diff_list *difflist = NULL;
|
||||
git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
|
||||
git_tree *otree=NULL, *ptree=NULL;
|
||||
|
||||
/* Get the trees from this commit and its parent */
|
||||
// TODO: check errors
|
||||
git_commit_tree(&otree, origin->commit);
|
||||
git_commit_tree(&ptree, parent);
|
||||
|
||||
/* Configure the diff */
|
||||
diffopts.context_lines = 0;
|
||||
diffopts.flags = GIT_DIFF_SKIP_BINARY_CHECK;
|
||||
|
||||
/* Check to see if files we're interested have changed */
|
||||
diffopts.pathspec.count = sb->blame->paths.length;
|
||||
diffopts.pathspec.strings = (char**)sb->blame->paths.contents;
|
||||
// TODO: check error
|
||||
git_diff_tree_to_tree(&difflist, sb->blame->repository, ptree, otree, &diffopts);
|
||||
|
||||
if (!git_diff_num_deltas(difflist)) {
|
||||
/* No changes; copy data */
|
||||
// TODO: check error
|
||||
get_origin(&porigin, sb, parent, origin->path);
|
||||
} else {
|
||||
git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT;
|
||||
int i;
|
||||
|
||||
/* Generate a full diff between the two trees */
|
||||
git_diff_list_free(difflist);
|
||||
diffopts.pathspec.count = 0;
|
||||
// TODO: check error
|
||||
git_diff_tree_to_tree(&difflist, sb->blame->repository, ptree, otree, &diffopts);
|
||||
|
||||
/* Let diff find renames */
|
||||
findopts.flags = GIT_DIFF_FIND_RENAMES;
|
||||
// TODO: check error
|
||||
git_diff_find_similar(difflist, &findopts);
|
||||
|
||||
/* Find one that matches */
|
||||
for (i=0; i<(int)git_diff_num_deltas(difflist); i++) {
|
||||
const git_diff_delta *delta;
|
||||
git_diff_get_patch(NULL, &delta, difflist, i);
|
||||
if (git_vector_bsearch(NULL, &sb->blame->paths, delta->new_file.path) != 0)
|
||||
continue;
|
||||
|
||||
git_vector_insert_sorted(&sb->blame->paths, (void*)git__strdup(delta->old_file.path), paths_on_dup);
|
||||
// TODO: check error
|
||||
make_origin(&porigin, parent, delta->old_file.path);
|
||||
}
|
||||
}
|
||||
|
||||
git_diff_list_free(difflist);
|
||||
git_tree_free(otree);
|
||||
git_tree_free(ptree);
|
||||
return porigin;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* The blobs of origin and porigin exactly match, so everything origin is
|
||||
* suspected for can be blamed on the parent.
|
||||
*/
|
||||
static void pass_whole_blame(struct scoreboard *sb,
|
||||
struct origin *origin, struct origin *porigin)
|
||||
{
|
||||
struct blame_entry *e;
|
||||
|
||||
if (!porigin->blob)
|
||||
git_object_lookup((git_object**)&porigin->blob, sb->blame->repository, git_blob_id(origin->blob),
|
||||
GIT_OBJ_BLOB);
|
||||
for (e=sb->ent; e; e=e->next) {
|
||||
if (!same_suspect(e->suspect, origin))
|
||||
continue;
|
||||
origin_incref(porigin);
|
||||
origin_decref(e->suspect);
|
||||
e->suspect = porigin;
|
||||
}
|
||||
}
|
||||
|
||||
static void pass_blame(struct scoreboard *sb, struct origin *origin, uint32_t opt)
|
||||
{
|
||||
git_commit *commit = origin->commit;
|
||||
int i, num_sg;
|
||||
struct origin *sg_buf[16];
|
||||
struct origin *porigin, **sg_origin = sg_buf;
|
||||
|
||||
GIT_UNUSED(opt);
|
||||
|
||||
num_sg = git_commit_parentcount(commit);
|
||||
if (!num_sg)
|
||||
goto finish;
|
||||
else if (num_sg < (int)ARRAY_SIZE(sg_buf))
|
||||
memset(sg_buf, 0, sizeof(sg_buf));
|
||||
else
|
||||
sg_origin = git__calloc(num_sg, sizeof(*sg_origin));
|
||||
|
||||
for (i=0; i<num_sg; i++) {
|
||||
git_commit *p;
|
||||
int j, same;
|
||||
|
||||
if (sg_origin[i])
|
||||
continue;
|
||||
|
||||
// TODO: check error
|
||||
git_commit_parent(&p, origin->commit, i);
|
||||
porigin = find_origin(sb, p, origin);
|
||||
|
||||
if (!porigin)
|
||||
continue;
|
||||
if (porigin->blob && origin->blob &&
|
||||
!git_oid_cmp(git_blob_id(porigin->blob), git_blob_id(origin->blob))) {
|
||||
pass_whole_blame(sb, origin, porigin);
|
||||
origin_decref(porigin);
|
||||
goto finish;
|
||||
}
|
||||
for (j = same = 0; j<i; j++)
|
||||
if (sg_origin[j] &&
|
||||
!git_oid_cmp(git_blob_id(sg_origin[j]->blob), git_blob_id(porigin->blob))) {
|
||||
same = 1;
|
||||
break;
|
||||
}
|
||||
if (!same)
|
||||
sg_origin[i] = porigin;
|
||||
else
|
||||
origin_decref(porigin);
|
||||
}
|
||||
|
||||
for (i=0; i<num_sg; i++) {
|
||||
struct origin *porigin = sg_origin[i];
|
||||
if (!porigin)
|
||||
continue;
|
||||
if (!origin->previous) {
|
||||
origin_incref(porigin);
|
||||
origin->previous = porigin;
|
||||
}
|
||||
if (pass_blame_to_parent(sb, origin, porigin))
|
||||
goto finish;
|
||||
}
|
||||
|
||||
/* TODO: optionally find moves in parents' files */
|
||||
|
||||
/* TODO: optionally find copies in parents' files */
|
||||
|
||||
finish:
|
||||
for (i=0; i<num_sg; i++)
|
||||
if (sg_origin[i])
|
||||
origin_decref(sg_origin[i]);
|
||||
if (sg_origin != sg_buf)
|
||||
git__free(sg_origin);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Origin is refcounted and usually we keep the blob contents to be
|
||||
* reused.
|
||||
*/
|
||||
struct origin *origin_incref(struct origin *o)
|
||||
{
|
||||
if (o)
|
||||
o->refcnt++;
|
||||
return o;
|
||||
}
|
||||
|
||||
void origin_decref(struct origin *o)
|
||||
{
|
||||
if (o && --o->refcnt <= 0) {
|
||||
if (o->previous)
|
||||
origin_decref(o->previous);
|
||||
git_blob_free(o->blob);
|
||||
git_commit_free(o->commit);
|
||||
git__free(o);
|
||||
}
|
||||
}
|
||||
|
||||
void assign_blame(struct scoreboard *sb, uint32_t opt)
|
||||
{
|
||||
while (true) {
|
||||
struct blame_entry *ent;
|
||||
struct origin *suspect = NULL;
|
||||
|
||||
/* Find a suspect to break down */
|
||||
for (ent = sb->ent; !suspect && ent; ent = ent->next)
|
||||
if (!ent->guilty)
|
||||
suspect = ent->suspect;
|
||||
if (!suspect)
|
||||
return; /* all done */
|
||||
|
||||
/* We'll use this suspect later in the loop, so hold on to it for now. */
|
||||
origin_incref(suspect);
|
||||
pass_blame(sb, suspect, opt);
|
||||
|
||||
/* Take responsibility for the remaining entries */
|
||||
for (ent = sb->ent; ent; ent = ent->next)
|
||||
if (same_suspect(ent->suspect, suspect))
|
||||
ent->guilty = 1;
|
||||
origin_decref(suspect);
|
||||
}
|
||||
}
|
||||
|
||||
void coalesce(struct scoreboard *sb)
|
||||
{
|
||||
struct blame_entry *ent, *next;
|
||||
|
||||
for (ent=sb->ent; ent && (next = ent->next); ent = next) {
|
||||
if (same_suspect(ent->suspect, next->suspect) &&
|
||||
ent->guilty == next->guilty &&
|
||||
ent->s_lno + ent->num_lines == next->s_lno)
|
||||
{
|
||||
ent->num_lines += next->num_lines;
|
||||
ent->next = next->next;
|
||||
if (ent->next)
|
||||
ent->next->prev = ent;
|
||||
origin_decref(next->suspect);
|
||||
git__free(next);
|
||||
ent->score = 0;
|
||||
next = ent; /* again */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
93
src/blame_git.h
Normal file
93
src/blame_git.h
Normal file
@ -0,0 +1,93 @@
|
||||
|
||||
#ifndef INCLUDE_blame_git__
|
||||
#define INCLUDE_blame_git__
|
||||
|
||||
#include "git2.h"
|
||||
#include "xdiff/xinclude.h"
|
||||
#include "blame.h"
|
||||
|
||||
/*
|
||||
* One blob in a commit that is being suspected
|
||||
*/
|
||||
struct origin {
|
||||
int refcnt;
|
||||
struct origin *previous;
|
||||
git_commit *commit;
|
||||
git_blob *blob;
|
||||
char path[];
|
||||
};
|
||||
|
||||
/*
|
||||
* Each group of lines is described by a blame_entry; it can be split
|
||||
* as we pass blame to the parents. They form a linked list in the
|
||||
* scoreboard structure, sorted by the target line number.
|
||||
*/
|
||||
struct blame_entry {
|
||||
struct blame_entry *prev;
|
||||
struct blame_entry *next;
|
||||
|
||||
/* the first line of this group in the final image;
|
||||
* internally all line numbers are 0 based.
|
||||
*/
|
||||
int lno;
|
||||
|
||||
/* how many lines this group has */
|
||||
int num_lines;
|
||||
|
||||
/* the commit that introduced this group into the final image */
|
||||
struct origin *suspect;
|
||||
|
||||
/* true if the suspect is truly guilty; false while we have not
|
||||
* checked if the group came from one of its parents.
|
||||
*/
|
||||
char guilty;
|
||||
|
||||
/* true if the entry has been scanned for copies in the current parent
|
||||
*/
|
||||
char scanned;
|
||||
|
||||
/* the line number of the first line of this group in the
|
||||
* suspect's file; internally all line numbers are 0 based.
|
||||
*/
|
||||
int s_lno;
|
||||
|
||||
/* how significant this entry is -- cached to avoid
|
||||
* scanning the lines over and over.
|
||||
*/
|
||||
unsigned score;
|
||||
};
|
||||
|
||||
/*
|
||||
* The current state of the blame assignment.
|
||||
*/
|
||||
struct scoreboard {
|
||||
/* the final commit (i.e. where we started digging from) */
|
||||
git_commit *final;
|
||||
const char *path;
|
||||
|
||||
/*
|
||||
* The contents in the final image.
|
||||
* Used by many functions to obtain contents of the nth line,
|
||||
* indexed with scoreboard.lineno[blame_entry.lno].
|
||||
*/
|
||||
const char *final_buf;
|
||||
git_off_t final_buf_size;
|
||||
|
||||
/* linked list of blames */
|
||||
struct blame_entry *ent;
|
||||
|
||||
/* look-up a line in the final buffer */
|
||||
int num_lines;
|
||||
|
||||
git_blame *blame;
|
||||
};
|
||||
|
||||
|
||||
int get_origin(struct origin **out, struct scoreboard *sb, git_commit *commit, const char *path);
|
||||
int make_origin(struct origin **out, git_commit *commit, const char *path);
|
||||
struct origin *origin_incref(struct origin *o);
|
||||
void origin_decref(struct origin *o);
|
||||
void assign_blame(struct scoreboard *sb, uint32_t flags);
|
||||
void coalesce(struct scoreboard *sb);
|
||||
|
||||
#endif
|
36
src/object.c
36
src/object.c
@ -364,3 +364,39 @@ int git_object_dup(git_object **dest, git_object *source)
|
||||
*dest = source;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int git_object_lookup_bypath(
|
||||
git_object **out,
|
||||
const git_object *treeish,
|
||||
const char *path,
|
||||
git_otype type)
|
||||
{
|
||||
int error = -1;
|
||||
git_tree *tree = NULL;
|
||||
git_tree_entry *entry = NULL;
|
||||
git_object *tmpobj = NULL;
|
||||
|
||||
assert(out && treeish && path);
|
||||
|
||||
if (((error = git_object_peel((git_object**)&tree, treeish, GIT_OBJ_TREE)) < 0) ||
|
||||
((error = git_tree_entry_bypath(&entry, tree, path)) < 0) ||
|
||||
((error = git_tree_entry_to_object(&tmpobj, git_object_owner(treeish), entry)) < 0))
|
||||
{
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (type == GIT_OBJ_ANY || git_object_type(tmpobj) == type) {
|
||||
*out = tmpobj;
|
||||
} else {
|
||||
giterr_set(GITERR_OBJECT,
|
||||
"object at path '%s' is not of the asked-for type %d",
|
||||
path, type);
|
||||
error = GIT_EINVALIDSPEC;
|
||||
git_object_free(tmpobj);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
git_tree_entry_free(entry);
|
||||
git_tree_free(tree);
|
||||
return error;
|
||||
}
|
||||
|
58
tests-clar/blame/blame_helpers.c
Normal file
58
tests-clar/blame/blame_helpers.c
Normal file
@ -0,0 +1,58 @@
|
||||
#include "blame_helpers.h"
|
||||
|
||||
void hunk_message(size_t idx, const git_blame_hunk *hunk, const char *fmt, ...)
|
||||
{
|
||||
va_list arglist;
|
||||
|
||||
printf("Hunk %zd (line %d +%d): ", idx,
|
||||
hunk->final_start_line_number, hunk->lines_in_hunk-1);
|
||||
|
||||
va_start(arglist, fmt);
|
||||
vprintf(fmt, arglist);
|
||||
va_end(arglist);
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
void check_blame_hunk_index(git_repository *repo, git_blame *blame, int idx,
|
||||
int start_line, int len, const char *commit_id, const char *orig_path)
|
||||
{
|
||||
char expected[41] = {0}, actual[41] = {0};
|
||||
const git_blame_hunk *hunk = git_blame_get_hunk_byindex(blame, idx);
|
||||
cl_assert(hunk);
|
||||
|
||||
if (!strncmp(commit_id, "0000", 4)) {
|
||||
strcpy(expected, "0000000000000000000000000000000000000000");
|
||||
} else {
|
||||
git_object *obj;
|
||||
cl_git_pass(git_revparse_single(&obj, repo, commit_id));
|
||||
git_oid_fmt(expected, git_object_id(obj));
|
||||
git_object_free(obj);
|
||||
}
|
||||
|
||||
if (hunk->final_start_line_number != start_line) {
|
||||
hunk_message(idx, hunk, "mismatched start line number: expected %d, got %d",
|
||||
start_line, hunk->final_start_line_number);
|
||||
}
|
||||
cl_assert_equal_i(hunk->final_start_line_number, start_line);
|
||||
|
||||
if (hunk->lines_in_hunk != len) {
|
||||
hunk_message(idx, hunk, "mismatched line count: expected %d, got %d",
|
||||
len, hunk->lines_in_hunk);
|
||||
}
|
||||
cl_assert_equal_i(hunk->lines_in_hunk, len);
|
||||
|
||||
git_oid_fmt(actual, &hunk->final_commit_id);
|
||||
if (strcmp(expected, actual)) {
|
||||
hunk_message(idx, hunk, "has mismatched original id (got %s, expected %s)\n",
|
||||
actual, expected);
|
||||
}
|
||||
cl_assert_equal_s(actual, expected);
|
||||
if (strcmp(hunk->orig_path, orig_path)) {
|
||||
hunk_message(idx, hunk, "has mismatched original path (got '%s', expected '%s')\n",
|
||||
hunk->orig_path, orig_path);
|
||||
}
|
||||
cl_assert_equal_s(hunk->orig_path, orig_path);
|
||||
}
|
||||
|
||||
|
15
tests-clar/blame/blame_helpers.h
Normal file
15
tests-clar/blame/blame_helpers.h
Normal file
@ -0,0 +1,15 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "blame.h"
|
||||
|
||||
void hunk_message(size_t idx, const git_blame_hunk *hunk, const char *fmt, ...);
|
||||
|
||||
void check_blame_hunk_index(
|
||||
git_repository *repo,
|
||||
git_blame *blame,
|
||||
int idx,
|
||||
int start_line,
|
||||
int len,
|
||||
const char *commit_id,
|
||||
const char *orig_path);
|
||||
|
||||
|
130
tests-clar/blame/buffer.c
Normal file
130
tests-clar/blame/buffer.c
Normal file
@ -0,0 +1,130 @@
|
||||
#include "blame_helpers.h"
|
||||
|
||||
git_repository *g_repo;
|
||||
git_blame *g_fileblame, *g_bufferblame;
|
||||
|
||||
void test_blame_buffer__initialize(void)
|
||||
{
|
||||
cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git")));
|
||||
cl_git_pass(git_blame_file(&g_fileblame, g_repo, "b.txt", NULL));
|
||||
g_bufferblame = NULL;
|
||||
}
|
||||
|
||||
void test_blame_buffer__cleanup(void)
|
||||
{
|
||||
git_blame_free(g_fileblame);
|
||||
git_blame_free(g_bufferblame);
|
||||
git_repository_free(g_repo);
|
||||
}
|
||||
|
||||
void test_blame_buffer__added_line(void)
|
||||
{
|
||||
const char *buffer = "\
|
||||
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
|
||||
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
|
||||
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
|
||||
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
|
||||
\n\
|
||||
abcdefg\n\
|
||||
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
|
||||
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
|
||||
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
|
||||
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
|
||||
\n\
|
||||
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
|
||||
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
|
||||
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
|
||||
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n";
|
||||
|
||||
cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer)));
|
||||
cl_assert_equal_i(5, git_blame_get_hunk_count(g_bufferblame));
|
||||
check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 1, "000000", "b.txt");
|
||||
}
|
||||
|
||||
void test_blame_buffer__deleted_line(void)
|
||||
{
|
||||
const char *buffer = "\
|
||||
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
|
||||
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
|
||||
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
|
||||
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
|
||||
\n\
|
||||
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
|
||||
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
|
||||
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
|
||||
\n\
|
||||
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
|
||||
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
|
||||
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
|
||||
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n";
|
||||
|
||||
cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer)));
|
||||
check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 3, "63d671eb", "b.txt");
|
||||
check_blame_hunk_index(g_repo, g_bufferblame, 3, 9, 1, "63d671eb", "b.txt");
|
||||
check_blame_hunk_index(g_repo, g_bufferblame, 4, 10, 5, "aa06ecca", "b.txt");
|
||||
}
|
||||
|
||||
void test_blame_buffer__add_splits_hunk(void)
|
||||
{
|
||||
const char *buffer = "\
|
||||
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
|
||||
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
|
||||
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
|
||||
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
|
||||
\n\
|
||||
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
|
||||
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
|
||||
abc\n\
|
||||
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
|
||||
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
|
||||
\n\
|
||||
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
|
||||
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
|
||||
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
|
||||
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n";
|
||||
|
||||
cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer)));
|
||||
check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 2, "63d671eb", "b.txt");
|
||||
check_blame_hunk_index(g_repo, g_bufferblame, 3, 8, 1, "00000000", "b.txt");
|
||||
check_blame_hunk_index(g_repo, g_bufferblame, 4, 9, 3, "63d671eb", "b.txt");
|
||||
}
|
||||
|
||||
void test_blame_buffer__delete_crosses_hunk_boundary(void)
|
||||
{
|
||||
const char *buffer = "\
|
||||
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
|
||||
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
|
||||
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
|
||||
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
|
||||
\n\
|
||||
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
|
||||
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n";
|
||||
|
||||
cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer)));
|
||||
check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 1, "63d671eb", "b.txt");
|
||||
check_blame_hunk_index(g_repo, g_bufferblame, 3, 7, 2, "aa06ecca", "b.txt");
|
||||
}
|
||||
|
||||
void test_blame_buffer__replace_line(void)
|
||||
{
|
||||
const char *buffer = "\
|
||||
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
|
||||
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
|
||||
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
|
||||
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
|
||||
\n\
|
||||
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
|
||||
abc\n\
|
||||
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
|
||||
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
|
||||
\n\
|
||||
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
|
||||
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
|
||||
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
|
||||
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n";
|
||||
|
||||
cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer)));
|
||||
check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 1, "63d671eb", "b.txt");
|
||||
check_blame_hunk_index(g_repo, g_bufferblame, 3, 7, 1, "00000000", "b.txt");
|
||||
check_blame_hunk_index(g_repo, g_bufferblame, 4, 8, 3, "63d671eb", "b.txt");
|
||||
}
|
56
tests-clar/blame/getters.c
Normal file
56
tests-clar/blame/getters.c
Normal file
@ -0,0 +1,56 @@
|
||||
#include "clar_libgit2.h"
|
||||
|
||||
#include "blame.h"
|
||||
|
||||
git_blame *g_blame;
|
||||
|
||||
void test_blame_getters__initialize(void)
|
||||
{
|
||||
size_t i;
|
||||
git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
|
||||
|
||||
git_blame_hunk hunks[] = {
|
||||
{ 3, {{0}}, 1, {{0}}, "a", 0},
|
||||
{ 3, {{0}}, 4, {{0}}, "b", 0},
|
||||
{ 3, {{0}}, 7, {{0}}, "c", 0},
|
||||
{ 3, {{0}}, 10, {{0}}, "d", 0},
|
||||
{ 3, {{0}}, 13, {{0}}, "e", 0},
|
||||
};
|
||||
|
||||
g_blame = git_blame__alloc(NULL, opts, "");
|
||||
|
||||
for (i=0; i<5; i++) {
|
||||
git_blame_hunk *h = git_blame__alloc_hunk();
|
||||
h->final_start_line_number = hunks[i].final_start_line_number;
|
||||
h->orig_path = git__strdup(hunks[i].orig_path);
|
||||
h->lines_in_hunk = hunks[i].lines_in_hunk;
|
||||
|
||||
git_vector_insert(&g_blame->hunks, h);
|
||||
}
|
||||
}
|
||||
|
||||
void test_blame_getters__cleanup(void)
|
||||
{
|
||||
git_blame_free(g_blame);
|
||||
}
|
||||
|
||||
|
||||
void test_blame_getters__byindex(void)
|
||||
{
|
||||
const git_blame_hunk *h = git_blame_get_hunk_byindex(g_blame, 2);
|
||||
cl_assert(h);
|
||||
cl_assert_equal_s(h->orig_path, "c");
|
||||
|
||||
h = git_blame_get_hunk_byindex(g_blame, 95);
|
||||
cl_assert_equal_p(h, NULL);
|
||||
}
|
||||
|
||||
void test_blame_getters__byline(void)
|
||||
{
|
||||
const git_blame_hunk *h = git_blame_get_hunk_byline(g_blame, 5);
|
||||
cl_assert(h);
|
||||
cl_assert_equal_s(h->orig_path, "b");
|
||||
|
||||
h = git_blame_get_hunk_byline(g_blame, 95);
|
||||
cl_assert_equal_p(h, NULL);
|
||||
}
|
71
tests-clar/blame/harder.c
Normal file
71
tests-clar/blame/harder.c
Normal file
@ -0,0 +1,71 @@
|
||||
#include "clar_libgit2.h"
|
||||
|
||||
#include "blame.h"
|
||||
|
||||
|
||||
/**
|
||||
* The test repo has a history that looks like this:
|
||||
*
|
||||
* * (A) bc7c5ac
|
||||
* |\
|
||||
* | * (B) aa06ecc
|
||||
* * | (C) 63d671e
|
||||
* |/
|
||||
* * (D) da23739
|
||||
* * (E) b99f7ac
|
||||
*
|
||||
*/
|
||||
|
||||
static git_repository *g_repo = NULL;
|
||||
|
||||
void test_blame_harder__initialize(void)
|
||||
{
|
||||
cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git")));
|
||||
}
|
||||
|
||||
void test_blame_harder__cleanup(void)
|
||||
{
|
||||
git_repository_free(g_repo);
|
||||
g_repo = NULL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void test_blame_harder__m(void)
|
||||
{
|
||||
/* TODO */
|
||||
git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
|
||||
|
||||
opts.flags = GIT_BLAME_TRACK_COPIES_SAME_FILE;
|
||||
}
|
||||
|
||||
|
||||
void test_blame_harder__c(void)
|
||||
{
|
||||
git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
|
||||
|
||||
/* Attribute the first hunk in b.txt to (E), since it was cut/pasted from
|
||||
* a.txt in (D).
|
||||
*/
|
||||
opts.flags = GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES;
|
||||
}
|
||||
|
||||
void test_blame_harder__cc(void)
|
||||
{
|
||||
git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
|
||||
|
||||
/* Attribute the second hunk in b.txt to (E), since it was copy/pasted from
|
||||
* a.txt in (C).
|
||||
*/
|
||||
opts.flags = GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES;
|
||||
}
|
||||
|
||||
void test_blame_harder__ccc(void)
|
||||
{
|
||||
git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
|
||||
|
||||
/* Attribute the third hunk in b.txt to (E). This hunk was deleted from
|
||||
* a.txt in (D), but reintroduced in (B).
|
||||
*/
|
||||
opts.flags = GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES;
|
||||
}
|
204
tests-clar/blame/simple.c
Normal file
204
tests-clar/blame/simple.c
Normal file
@ -0,0 +1,204 @@
|
||||
#include "blame_helpers.h"
|
||||
|
||||
/*
|
||||
* $ git blame -s branch_file.txt
|
||||
* orig line no final line no
|
||||
* commit V author timestamp V
|
||||
* c47800c7 1 (Scott Chacon 2010-05-25 11:58:14 -0700 1
|
||||
* a65fedf3 2 (Scott Chacon 2011-08-09 19:33:46 -0700 2
|
||||
*/
|
||||
void test_blame_simple__trivial_testrepo(void)
|
||||
{
|
||||
git_blame *blame = NULL;
|
||||
git_repository *repo;
|
||||
cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo/.gitted")));
|
||||
cl_git_pass(git_blame_file(&blame, repo, "branch_file.txt", NULL));
|
||||
|
||||
cl_assert_equal_i(2, git_blame_get_hunk_count(blame));
|
||||
check_blame_hunk_index(repo, blame, 0, 1, 1, "c47800c7", "branch_file.txt");
|
||||
check_blame_hunk_index(repo, blame, 1, 2, 1, "a65fedf3", "branch_file.txt");
|
||||
|
||||
git_blame_free(blame);
|
||||
git_repository_free(repo);
|
||||
}
|
||||
|
||||
/*
|
||||
* $ git blame -n b.txt
|
||||
* orig line no final line no
|
||||
* commit V author timestamp V
|
||||
* da237394 1 (Ben Straub 2013-02-12 15:11:30 -0800 1
|
||||
* da237394 2 (Ben Straub 2013-02-12 15:11:30 -0800 2
|
||||
* da237394 3 (Ben Straub 2013-02-12 15:11:30 -0800 3
|
||||
* da237394 4 (Ben Straub 2013-02-12 15:11:30 -0800 4
|
||||
* ^b99f7ac 1 (Ben Straub 2013-02-12 15:10:12 -0800 5
|
||||
* 63d671eb 6 (Ben Straub 2013-02-12 15:13:04 -0800 6
|
||||
* 63d671eb 7 (Ben Straub 2013-02-12 15:13:04 -0800 7
|
||||
* 63d671eb 8 (Ben Straub 2013-02-12 15:13:04 -0800 8
|
||||
* 63d671eb 9 (Ben Straub 2013-02-12 15:13:04 -0800 9
|
||||
* 63d671eb 10 (Ben Straub 2013-02-12 15:13:04 -0800 10
|
||||
* aa06ecca 6 (Ben Straub 2013-02-12 15:14:46 -0800 11
|
||||
* aa06ecca 7 (Ben Straub 2013-02-12 15:14:46 -0800 12
|
||||
* aa06ecca 8 (Ben Straub 2013-02-12 15:14:46 -0800 13
|
||||
* aa06ecca 9 (Ben Straub 2013-02-12 15:14:46 -0800 14
|
||||
* aa06ecca 10 (Ben Straub 2013-02-12 15:14:46 -0800 15
|
||||
*/
|
||||
void test_blame_simple__trivial_blamerepo(void)
|
||||
{
|
||||
git_blame *blame = NULL;
|
||||
git_repository *repo;
|
||||
cl_git_pass(git_repository_open(&repo, cl_fixture("blametest.git")));
|
||||
cl_git_pass(git_blame_file(&blame, repo, "b.txt", NULL));
|
||||
|
||||
cl_assert_equal_i(4, git_blame_get_hunk_count(blame));
|
||||
check_blame_hunk_index(repo, blame, 0, 1, 4, "da237394", "b.txt");
|
||||
check_blame_hunk_index(repo, blame, 1, 5, 1, "b99f7ac0", "b.txt");
|
||||
check_blame_hunk_index(repo, blame, 2, 6, 5, "63d671eb", "b.txt");
|
||||
check_blame_hunk_index(repo, blame, 3, 11, 5, "aa06ecca", "b.txt");
|
||||
|
||||
git_blame_free(blame);
|
||||
git_repository_free(repo);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* $ git blame -n 359fc2d -- include/git2.h
|
||||
* orig line no final line no
|
||||
* commit orig path V author timestamp V
|
||||
* d12299fe src/git.h 1 (Vicent Martí 2010-12-03 22:22:10 +0200 1
|
||||
* 359fc2d2 include/git2.h 2 (Edward Thomson 2013-01-08 17:07:25 -0600 2
|
||||
* d12299fe src/git.h 5 (Vicent Martí 2010-12-03 22:22:10 +0200 3
|
||||
* bb742ede include/git2.h 4 (Vicent Martí 2011-09-19 01:54:32 +0300 4
|
||||
* bb742ede include/git2.h 5 (Vicent Martí 2011-09-19 01:54:32 +0300 5
|
||||
* d12299fe src/git.h 24 (Vicent Martí 2010-12-03 22:22:10 +0200 6
|
||||
* d12299fe src/git.h 25 (Vicent Martí 2010-12-03 22:22:10 +0200 7
|
||||
* d12299fe src/git.h 26 (Vicent Martí 2010-12-03 22:22:10 +0200 8
|
||||
* d12299fe src/git.h 27 (Vicent Martí 2010-12-03 22:22:10 +0200 9
|
||||
* d12299fe src/git.h 28 (Vicent Martí 2010-12-03 22:22:10 +0200 10
|
||||
* 96fab093 include/git2.h 11 (Sven Strickroth 2011-10-09 18:37:41 +0200 11
|
||||
* 9d1dcca2 src/git2.h 33 (Vicent Martí 2011-02-07 10:35:58 +0200 12
|
||||
* 44908fe7 src/git2.h 29 (Vicent Martí 2010-12-06 23:03:16 +0200 13
|
||||
* a15c550d include/git2.h 14 (Vicent Martí 2011-11-16 14:09:44 +0100 14
|
||||
* 44908fe7 src/git2.h 30 (Vicent Martí 2010-12-06 23:03:16 +0200 15
|
||||
* d12299fe src/git.h 32 (Vicent Martí 2010-12-03 22:22:10 +0200 16
|
||||
* 44908fe7 src/git2.h 33 (Vicent Martí 2010-12-06 23:03:16 +0200 17
|
||||
* d12299fe src/git.h 34 (Vicent Martí 2010-12-03 22:22:10 +0200 18
|
||||
* 44908fe7 src/git2.h 35 (Vicent Martí 2010-12-06 23:03:16 +0200 19
|
||||
* 638c2ca4 src/git2.h 36 (Vicent Martí 2010-12-18 02:10:25 +0200 20
|
||||
* 44908fe7 src/git2.h 36 (Vicent Martí 2010-12-06 23:03:16 +0200 21
|
||||
* d12299fe src/git.h 37 (Vicent Martí 2010-12-03 22:22:10 +0200 22
|
||||
* 44908fe7 src/git2.h 38 (Vicent Martí 2010-12-06 23:03:16 +0200 23
|
||||
* 44908fe7 src/git2.h 39 (Vicent Martí 2010-12-06 23:03:16 +0200 24
|
||||
* bf787bd8 include/git2.h 25 (Carlos Martín Nieto 2012-04-08 18:56:50 +0200 25
|
||||
* 0984c876 include/git2.h 26 (Scott J. Goldman 2012-11-28 18:27:43 -0800 26
|
||||
* 2f8a8ab2 src/git2.h 41 (Vicent Martí 2011-01-29 01:56:25 +0200 27
|
||||
* 27df4275 include/git2.h 47 (Michael Schubert 2011-06-28 14:13:12 +0200 28
|
||||
* a346992f include/git2.h 28 (Ben Straub 2012-05-10 09:47:14 -0700 29
|
||||
* d12299fe src/git.h 40 (Vicent Martí 2010-12-03 22:22:10 +0200 30
|
||||
* 44908fe7 src/git2.h 41 (Vicent Martí 2010-12-06 23:03:16 +0200 31
|
||||
* 44908fe7 src/git2.h 42 (Vicent Martí 2010-12-06 23:03:16 +0200 32
|
||||
* 44908fe7 src/git2.h 43 (Vicent Martí 2010-12-06 23:03:16 +0200 33
|
||||
* 44908fe7 src/git2.h 44 (Vicent Martí 2010-12-06 23:03:16 +0200 34
|
||||
* 44908fe7 src/git2.h 45 (Vicent Martí 2010-12-06 23:03:16 +0200 35
|
||||
* 65b09b1d include/git2.h 33 (Russell Belfer 2012-02-02 18:03:43 -0800 36
|
||||
* d12299fe src/git.h 46 (Vicent Martí 2010-12-03 22:22:10 +0200 37
|
||||
* 44908fe7 src/git2.h 47 (Vicent Martí 2010-12-06 23:03:16 +0200 38
|
||||
* 5d4cd003 include/git2.h 55 (Carlos Martín Nieto 2011-03-28 17:02:45 +0200 39
|
||||
* 41fb1ca0 include/git2.h 39 (Philip Kelley 2012-10-29 13:41:14 -0400 40
|
||||
* 2dc31040 include/git2.h 56 (Carlos Martín Nieto 2011-06-20 18:58:57 +0200 41
|
||||
* 764df57e include/git2.h 40 (Ben Straub 2012-06-15 13:14:43 -0700 42
|
||||
* 5280f4e6 include/git2.h 41 (Ben Straub 2012-07-31 19:39:06 -0700 43
|
||||
* 613d5eb9 include/git2.h 43 (Philip Kelley 2012-11-28 11:42:37 -0500 44
|
||||
* d12299fe src/git.h 48 (Vicent Martí 2010-12-03 22:22:10 +0200 45
|
||||
* 111ee3fe include/git2.h 41 (Vicent Martí 2012-07-11 14:37:26 +0200 46
|
||||
* f004c4a8 include/git2.h 44 (Russell Belfer 2012-08-21 17:26:39 -0700 47
|
||||
* 111ee3fe include/git2.h 42 (Vicent Martí 2012-07-11 14:37:26 +0200 48
|
||||
* 9c82357b include/git2.h 58 (Carlos Martín Nieto 2011-06-17 18:13:14 +0200 49
|
||||
* d6258deb include/git2.h 61 (Carlos Martín Nieto 2011-06-25 15:10:09 +0200 50
|
||||
* b311e313 include/git2.h 63 (Julien Miotte 2011-07-27 18:31:13 +0200 51
|
||||
* 3412391d include/git2.h 63 (Carlos Martín Nieto 2011-07-07 11:47:31 +0200 52
|
||||
* bfc9ca59 include/git2.h 43 (Russell Belfer 2012-03-28 16:45:36 -0700 53
|
||||
* bf477ed4 include/git2.h 44 (Michael Schubert 2012-02-15 00:33:38 +0100 54
|
||||
* edebceff include/git2.h 46 (nulltoken 2012-05-01 13:57:45 +0200 55
|
||||
* 743a4b3b include/git2.h 48 (nulltoken 2012-06-15 22:24:59 +0200 56
|
||||
* 0a32dca5 include/git2.h 54 (Michael Schubert 2012-08-19 22:26:32 +0200 57
|
||||
* 590fb68b include/git2.h 55 (nulltoken 2012-10-04 13:47:45 +0200 58
|
||||
* bf477ed4 include/git2.h 45 (Michael Schubert 2012-02-15 00:33:38 +0100 59
|
||||
* d12299fe src/git.h 49 (Vicent Martí 2010-12-03 22:22:10 +0200 60
|
||||
*/
|
||||
void test_blame_simple__trivial_libgit2(void)
|
||||
{
|
||||
git_repository *repo;
|
||||
git_blame *blame;
|
||||
git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
|
||||
git_object *obj;
|
||||
|
||||
cl_git_pass(git_repository_open(&repo, cl_fixture("../..")));
|
||||
|
||||
/* This test can't work on a shallow clone */
|
||||
if (git_repository_is_shallow(repo)) {
|
||||
git_repository_free(repo);
|
||||
return;
|
||||
}
|
||||
|
||||
cl_git_pass(git_revparse_single(&obj, repo, "359fc2d"));
|
||||
git_oid_cpy(&opts.newest_commit, git_object_id(obj));
|
||||
git_object_free(obj);
|
||||
|
||||
cl_git_pass(git_blame_file(&blame, repo, "include/git2.h", &opts));
|
||||
|
||||
check_blame_hunk_index(repo, blame, 0, 1, 1, "d12299fe", "src/git.h");
|
||||
check_blame_hunk_index(repo, blame, 1, 2, 1, "359fc2d2", "include/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 2, 3, 1, "d12299fe", "src/git.h");
|
||||
check_blame_hunk_index(repo, blame, 3, 4, 2, "bb742ede", "include/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 4, 6, 5, "d12299fe", "src/git.h");
|
||||
check_blame_hunk_index(repo, blame, 5, 11, 1, "96fab093", "include/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 6, 12, 1, "9d1dcca2", "src/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 7, 13, 1, "44908fe7", "src/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 8, 14, 1, "a15c550d", "include/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 9, 15, 1, "44908fe7", "src/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 10, 16, 1, "d12299fe", "src/git.h");
|
||||
check_blame_hunk_index(repo, blame, 11, 17, 1, "44908fe7", "src/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 12, 18, 1, "d12299fe", "src/git.h");
|
||||
check_blame_hunk_index(repo, blame, 13, 19, 1, "44908fe7", "src/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 14, 20, 1, "638c2ca4", "src/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 15, 21, 1, "44908fe7", "src/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 16, 22, 1, "d12299fe", "src/git.h");
|
||||
check_blame_hunk_index(repo, blame, 17, 23, 2, "44908fe7", "src/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 18, 25, 1, "bf787bd8", "include/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 19, 26, 1, "0984c876", "include/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 20, 27, 1, "2f8a8ab2", "src/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 21, 28, 1, "27df4275", "include/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 22, 29, 1, "a346992f", "include/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 23, 30, 1, "d12299fe", "src/git.h");
|
||||
check_blame_hunk_index(repo, blame, 24, 31, 5, "44908fe7", "src/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 25, 36, 1, "65b09b1d", "include/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 26, 37, 1, "d12299fe", "src/git.h");
|
||||
check_blame_hunk_index(repo, blame, 27, 38, 1, "44908fe7", "src/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 28, 39, 1, "5d4cd003", "include/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 29, 40, 1, "41fb1ca0", "include/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 30, 41, 1, "2dc31040", "include/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 31, 42, 1, "764df57e", "include/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 32, 43, 1, "5280f4e6", "include/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 33, 44, 1, "613d5eb9", "include/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 34, 45, 1, "d12299fe", "src/git.h");
|
||||
check_blame_hunk_index(repo, blame, 35, 46, 1, "111ee3fe", "include/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 36, 47, 1, "f004c4a8", "include/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 37, 48, 1, "111ee3fe", "include/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 38, 49, 1, "9c82357b", "include/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 39, 50, 1, "d6258deb", "include/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 40, 51, 1, "b311e313", "include/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 41, 52, 1, "3412391d", "include/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 42, 53, 1, "bfc9ca59", "include/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 43, 54, 1, "bf477ed4", "include/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 44, 55, 1, "edebceff", "include/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 45, 56, 1, "743a4b3b", "include/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 46, 57, 1, "0a32dca5", "include/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 47, 58, 1, "590fb68b", "include/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 48, 59, 1, "bf477ed4", "include/git2.h");
|
||||
check_blame_hunk_index(repo, blame, 49, 60, 1, "d12299fe", "src/git.h");
|
||||
|
||||
git_blame_free(blame);
|
||||
git_repository_free(repo);
|
||||
}
|
||||
|
||||
/* TODO: no newline at end of file? */
|
83
tests-clar/object/lookupbypath.c
Normal file
83
tests-clar/object/lookupbypath.c
Normal file
@ -0,0 +1,83 @@
|
||||
#include "clar_libgit2.h"
|
||||
|
||||
#include "repository.h"
|
||||
|
||||
static git_repository *g_repo;
|
||||
static git_tree *g_root_tree;
|
||||
static git_commit *g_head_commit;
|
||||
static git_object *g_expectedobject,
|
||||
*g_actualobject;
|
||||
|
||||
void test_object_lookupbypath__initialize(void)
|
||||
{
|
||||
git_reference *head;
|
||||
git_tree_entry *tree_entry;
|
||||
|
||||
cl_git_pass(git_repository_open(&g_repo, cl_fixture("attr/.gitted")));
|
||||
|
||||
cl_git_pass(git_repository_head(&head, g_repo));
|
||||
cl_git_pass(git_reference_peel((git_object**)&g_head_commit, head, GIT_OBJ_COMMIT));
|
||||
cl_git_pass(git_commit_tree(&g_root_tree, g_head_commit));
|
||||
cl_git_pass(git_tree_entry_bypath(&tree_entry, g_root_tree, "subdir/subdir_test2.txt"));
|
||||
cl_git_pass(git_object_lookup(&g_expectedobject, g_repo, git_tree_entry_id(tree_entry),
|
||||
GIT_OBJ_ANY));
|
||||
|
||||
git_tree_entry_free(tree_entry);
|
||||
git_reference_free(head);
|
||||
|
||||
g_actualobject = NULL;
|
||||
}
|
||||
void test_object_lookupbypath__cleanup(void)
|
||||
{
|
||||
git_object_free(g_actualobject);
|
||||
git_object_free(g_expectedobject);
|
||||
git_tree_free(g_root_tree);
|
||||
git_commit_free(g_head_commit);
|
||||
g_expectedobject = NULL;
|
||||
git_repository_free(g_repo);
|
||||
g_repo = NULL;
|
||||
}
|
||||
|
||||
void test_object_lookupbypath__errors(void)
|
||||
{
|
||||
cl_assert_equal_i(GIT_EINVALIDSPEC,
|
||||
git_object_lookup_bypath(&g_actualobject, (git_object*)g_root_tree,
|
||||
"subdir/subdir_test2.txt", GIT_OBJ_TREE)); // It's not a tree
|
||||
cl_assert_equal_i(GIT_ENOTFOUND,
|
||||
git_object_lookup_bypath(&g_actualobject, (git_object*)g_root_tree,
|
||||
"file/doesnt/exist", GIT_OBJ_ANY));
|
||||
}
|
||||
|
||||
void test_object_lookupbypath__from_root_tree(void)
|
||||
{
|
||||
cl_git_pass(git_object_lookup_bypath(&g_actualobject, (git_object*)g_root_tree,
|
||||
"subdir/subdir_test2.txt", GIT_OBJ_BLOB));
|
||||
cl_assert_equal_i(0, git_oid_cmp(git_object_id(g_expectedobject),
|
||||
git_object_id(g_actualobject)));
|
||||
}
|
||||
|
||||
void test_object_lookupbypath__from_head_commit(void)
|
||||
{
|
||||
cl_git_pass(git_object_lookup_bypath(&g_actualobject, (git_object*)g_head_commit,
|
||||
"subdir/subdir_test2.txt", GIT_OBJ_BLOB));
|
||||
cl_assert_equal_i(0, git_oid_cmp(git_object_id(g_expectedobject),
|
||||
git_object_id(g_actualobject)));
|
||||
}
|
||||
|
||||
void test_object_lookupbypath__from_subdir_tree(void)
|
||||
{
|
||||
git_tree_entry *entry = NULL;
|
||||
git_tree *tree = NULL;
|
||||
|
||||
cl_git_pass(git_tree_entry_bypath(&entry, g_root_tree, "subdir"));
|
||||
cl_git_pass(git_tree_lookup(&tree, g_repo, git_tree_entry_id(entry)));
|
||||
|
||||
cl_git_pass(git_object_lookup_bypath(&g_actualobject, (git_object*)tree,
|
||||
"subdir_test2.txt", GIT_OBJ_BLOB));
|
||||
cl_assert_equal_i(0, git_oid_cmp(git_object_id(g_expectedobject),
|
||||
git_object_id(g_actualobject)));
|
||||
|
||||
git_tree_entry_free(entry);
|
||||
git_tree_free(tree);
|
||||
}
|
||||
|
1
tests-clar/resources/blametest.git/HEAD
Normal file
1
tests-clar/resources/blametest.git/HEAD
Normal file
@ -0,0 +1 @@
|
||||
ref: refs/heads/master
|
5
tests-clar/resources/blametest.git/config
Normal file
5
tests-clar/resources/blametest.git/config
Normal file
@ -0,0 +1,5 @@
|
||||
[core]
|
||||
repositoryformatversion = 0
|
||||
filemode = true
|
||||
bare = true
|
||||
ignorecase = true
|
1
tests-clar/resources/blametest.git/description
Normal file
1
tests-clar/resources/blametest.git/description
Normal file
@ -0,0 +1 @@
|
||||
Unnamed repository; edit this file 'description' to name the repository.
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,3 @@
|
||||
x•<>A E]sŠÙu¥Ê@bŒ±GðÀPë¢Azk<<3C>ù’—¼Ÿ×y~60–I³“1S´’=“-6÷FfC.†ìcp½õе,
$b
|
||||
¶8MF
|
||||
ᨹO!1eïÈ<C3AF>ò]TqkÓZáV¸··çô¾>žmÚÒ)¯ó49d<39>ì-Ñ#ª<>îmüg©áw©ºTK@Î
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1 @@
|
||||
x•ЏKВ0DYзЮuІг6I%„P9'ИЗЎ]фЈђЮџ"NАМоI#Ѕ‰л<O4б©€YPи(O.SЫзФЪЬ yЙЙ
FV›/ІTH^іеѕC¬“ЖД<D096>ЙvЎ–Ј3мrъЖ+їЧq-0ИПZьаЮчЧTЗ=\в:ЯЂШ %tОАў:ибVеї•zь.5CуЄЉA@
|
Binary file not shown.
Binary file not shown.
@ -0,0 +1,2 @@
|
||||
x•ŽK
|
||||
1]ç½›•ÒIg2Yˆˆâ <A>3‹L vîoÀ¸{õR-eÐΤ1ƒ#ŽóBÆ0<C386>©}¶s˜9xãí‹R6d1’S¡ËZÜx‡§´Ð#œãçúÞdíñ”j¹€&‡‹F§
Ñ#ªAGKø?KݧÇôgõ2r
|
@ -0,0 +1,3 @@
|
||||
x•<>AnÃ0{Ö+tË))J´AÛ<!/ eºÉÁqàÊÿ¯<C3BF>¢Èu±³Àl[æùÞc*òÖW÷èÅ26t-DS¨V5C2<43>ÀŠN#§Lá©«?zdy@7Jc*àY+
ÌnÖ¼bݧЅô¿¯
|
||||
ì)·¬#gJjBH^
|
||||
7•dšMâtë·e<C2B7>_þˆ×¾êfñd?ß÷~Ûì½-ó9"1B<12>Pžî.Ý_£Âåï‚Ãç!ü&íO
|
Binary file not shown.
@ -0,0 +1 @@
|
||||
x+)JMU03c040031QHÔ+©(a˜Ñyíihyâª>3ö<í^¹G¥nÕ@$<24>HÉ\;ÍMê<4D>oã¶œ<C2B6>úѬ‹£Æ¤
|
@ -0,0 +1,4 @@
|
||||
x•<>K
|
||||
Â0@]÷³ëJ™|'"¢ÞÀ$“ÔvÑVbzžÀíƒïÉ:ÏSðÐj)<29>Ñif£Ñ‘äDNSÆdOÌbs§Ñ[ìÞ±–¥Ab(
|
||||
¦Y;“ƒfç¬(‚˜„ƒ‰®‹[×
|
||||
·²À³Õ¸%8§Ïõ5µqK'Yç(ã‘zF8b@ìvº·µòŸÕÝKý£ÿc–?S
|
Binary file not shown.
Binary file not shown.
1
tests-clar/resources/blametest.git/refs/heads/master
Normal file
1
tests-clar/resources/blametest.git/refs/heads/master
Normal file
@ -0,0 +1 @@
|
||||
bc7c5ac2bafe828a68e9d1d460343718d6fbe136
|
Loading…
Reference in New Issue
Block a user