Commit ceab4e26 by Ben Straub

Port blame from git.git

parent 54993167
......@@ -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,11 +53,10 @@ 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).
* - `max_line` is the last line in the file to blame. The default is the last
......@@ -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
*
......
/*
* 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;
}
#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
/*
* 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 */
}
}
}
#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
......@@ -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;
}
#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);
}
#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);
#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");
}
#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);
}
#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;
}
#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? */
#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);
}
[core]
repositoryformatversion = 0
filemode = true
bare = true
ignorecase = true
Unnamed repository; edit this file 'description' to name the repository.
bc7c5ac2bafe828a68e9d1d460343718d6fbe136
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment