summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/git2/blame.h69
-rw-r--r--include/git2/object.h19
-rw-r--r--src/blame.c492
-rw-r--r--src/blame.h37
-rw-r--r--src/blame_git.c591
-rw-r--r--src/blame_git.h93
-rw-r--r--src/object.c36
-rw-r--r--tests-clar/blame/blame_helpers.c58
-rw-r--r--tests-clar/blame/blame_helpers.h15
-rw-r--r--tests-clar/blame/buffer.c130
-rw-r--r--tests-clar/blame/getters.c56
-rw-r--r--tests-clar/blame/harder.c71
-rw-r--r--tests-clar/blame/simple.c204
-rw-r--r--tests-clar/object/lookupbypath.c83
-rw-r--r--tests-clar/resources/blametest.git/HEAD1
-rw-r--r--tests-clar/resources/blametest.git/config5
-rw-r--r--tests-clar/resources/blametest.git/description1
-rw-r--r--tests-clar/resources/blametest.git/objects/0c/bab4d45fd61e55a1c9697f9f9cb07a12e15448bin0 -> 46 bytes
-rw-r--r--tests-clar/resources/blametest.git/objects/1a/ac69ae5d96461afc4d81d0066cb12f5b05a35bbin0 -> 28 bytes
-rw-r--r--tests-clar/resources/blametest.git/objects/1b/5f0775af166331c854bd8d1bca3450eaf2532abin0 -> 35 bytes
-rw-r--r--tests-clar/resources/blametest.git/objects/48/2f2c370e35c2c314fc1f96db2beb33f955a26abin0 -> 35 bytes
-rw-r--r--tests-clar/resources/blametest.git/objects/63/d671eb32d250e4a83766ebbc60e818c1e1e93a3
-rw-r--r--tests-clar/resources/blametest.git/objects/63/eb57322e363e18d460da5ea8284f3cd2340b36bin0 -> 76 bytes
-rw-r--r--tests-clar/resources/blametest.git/objects/8b/137891791fe96927ad78e64b0aad7bded08bdcbin0 -> 16 bytes
-rw-r--r--tests-clar/resources/blametest.git/objects/96/679d59cf9f74d69b3c920f258559b5e8c9a18abin0 -> 47 bytes
-rw-r--r--tests-clar/resources/blametest.git/objects/98/89d6e5557761aa8e3607e80c874a6dc51ada7cbin0 -> 43 bytes
-rw-r--r--tests-clar/resources/blametest.git/objects/aa/06ecca6c4ad6432ab9313e556ca92ba4bcf9e91
-rw-r--r--tests-clar/resources/blametest.git/objects/b1/76dfc3a4dc8734e4c579f77236a9c8d0a965d2bin0 -> 76 bytes
-rw-r--r--tests-clar/resources/blametest.git/objects/b9/0bb887b7c03750ae6b352ffe76ab9d2e86ee7dbin0 -> 56 bytes
-rw-r--r--tests-clar/resources/blametest.git/objects/b9/9f7ac0b88909253d829554c14af488c3b0f3a52
-rw-r--r--tests-clar/resources/blametest.git/objects/bc/7c5ac2bafe828a68e9d1d460343718d6fbe1363
-rw-r--r--tests-clar/resources/blametest.git/objects/cf/e0e1e1e3ba18f149fd47f5e1aef6016b2260c3bin0 -> 76 bytes
-rw-r--r--tests-clar/resources/blametest.git/objects/d0/67729932057cdb7527a833d6799c4ddc5206401
-rw-r--r--tests-clar/resources/blametest.git/objects/da/237394e6132d20d30f175b9b73c8638fddddda4
-rw-r--r--tests-clar/resources/blametest.git/objects/e5/b41c1ea533f87388ab69b13baf0b5a562d6243bin0 -> 76 bytes
-rw-r--r--tests-clar/resources/blametest.git/objects/ef/32df4d259143933715c74951f932d9892364d1bin0 -> 42 bytes
-rw-r--r--tests-clar/resources/blametest.git/refs/heads/master1
37 files changed, 1946 insertions, 30 deletions
diff --git a/include/git2/blame.h b/include/git2/blame.h
index 443b33503..fe1311362 100644
--- a/include/git2/blame.h
+++ b/include/git2/blame.h
@@ -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
diff --git a/include/git2/object.h b/include/git2/object.h
index f74f3dfd1..7ec17cd8e 100644
--- a/include/git2/object.h
+++ b/include/git2/object.h
@@ -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
*
diff --git a/src/blame.c b/src/blame.c
new file mode 100644
index 000000000..30d65f02c
--- /dev/null
+++ b/src/blame.c
@@ -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;
+}
diff --git a/src/blame.h b/src/blame.h
new file mode 100644
index 000000000..260d5b5a1
--- /dev/null
+++ b/src/blame.h
@@ -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
diff --git a/src/blame_git.c b/src/blame_git.c
new file mode 100644
index 000000000..0dae2d9c1
--- /dev/null
+++ b/src/blame_git.c
@@ -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 */
+ }
+ }
+}
+
diff --git a/src/blame_git.h b/src/blame_git.h
new file mode 100644
index 000000000..89ebc3228
--- /dev/null
+++ b/src/blame_git.h
@@ -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
diff --git a/src/object.c b/src/object.c
index 9b8ccdd3e..53666ffe4 100644
--- a/src/object.c
+++ b/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;
+}
diff --git a/tests-clar/blame/blame_helpers.c b/tests-clar/blame/blame_helpers.c
new file mode 100644
index 000000000..25c62a1ff
--- /dev/null
+++ b/tests-clar/blame/blame_helpers.c
@@ -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);
+}
+
+
diff --git a/tests-clar/blame/blame_helpers.h b/tests-clar/blame/blame_helpers.h
new file mode 100644
index 000000000..15fa1c33e
--- /dev/null
+++ b/tests-clar/blame/blame_helpers.h
@@ -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);
+
+
diff --git a/tests-clar/blame/buffer.c b/tests-clar/blame/buffer.c
new file mode 100644
index 000000000..20435eb6c
--- /dev/null
+++ b/tests-clar/blame/buffer.c
@@ -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");
+}
diff --git a/tests-clar/blame/getters.c b/tests-clar/blame/getters.c
new file mode 100644
index 000000000..82e0de59e
--- /dev/null
+++ b/tests-clar/blame/getters.c
@@ -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);
+}
diff --git a/tests-clar/blame/harder.c b/tests-clar/blame/harder.c
new file mode 100644
index 000000000..7c4dd4f74
--- /dev/null
+++ b/tests-clar/blame/harder.c
@@ -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;
+}
diff --git a/tests-clar/blame/simple.c b/tests-clar/blame/simple.c
new file mode 100644
index 000000000..4e2d88653
--- /dev/null
+++ b/tests-clar/blame/simple.c
@@ -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? */
diff --git a/tests-clar/object/lookupbypath.c b/tests-clar/object/lookupbypath.c
new file mode 100644
index 000000000..31aac7647
--- /dev/null
+++ b/tests-clar/object/lookupbypath.c
@@ -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);
+}
+
diff --git a/tests-clar/resources/blametest.git/HEAD b/tests-clar/resources/blametest.git/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests-clar/resources/blametest.git/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests-clar/resources/blametest.git/config b/tests-clar/resources/blametest.git/config
new file mode 100644
index 000000000..c53d818dd
--- /dev/null
+++ b/tests-clar/resources/blametest.git/config
@@ -0,0 +1,5 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = true
+ ignorecase = true
diff --git a/tests-clar/resources/blametest.git/description b/tests-clar/resources/blametest.git/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests-clar/resources/blametest.git/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests-clar/resources/blametest.git/objects/0c/bab4d45fd61e55a1c9697f9f9cb07a12e15448 b/tests-clar/resources/blametest.git/objects/0c/bab4d45fd61e55a1c9697f9f9cb07a12e15448
new file mode 100644
index 000000000..90331cef9
--- /dev/null
+++ b/tests-clar/resources/blametest.git/objects/0c/bab4d45fd61e55a1c9697f9f9cb07a12e15448
Binary files differ
diff --git a/tests-clar/resources/blametest.git/objects/1a/ac69ae5d96461afc4d81d0066cb12f5b05a35b b/tests-clar/resources/blametest.git/objects/1a/ac69ae5d96461afc4d81d0066cb12f5b05a35b
new file mode 100644
index 000000000..71890274a
--- /dev/null
+++ b/tests-clar/resources/blametest.git/objects/1a/ac69ae5d96461afc4d81d0066cb12f5b05a35b
Binary files differ
diff --git a/tests-clar/resources/blametest.git/objects/1b/5f0775af166331c854bd8d1bca3450eaf2532a b/tests-clar/resources/blametest.git/objects/1b/5f0775af166331c854bd8d1bca3450eaf2532a
new file mode 100644
index 000000000..e66430637
--- /dev/null
+++ b/tests-clar/resources/blametest.git/objects/1b/5f0775af166331c854bd8d1bca3450eaf2532a
Binary files differ
diff --git a/tests-clar/resources/blametest.git/objects/48/2f2c370e35c2c314fc1f96db2beb33f955a26a b/tests-clar/resources/blametest.git/objects/48/2f2c370e35c2c314fc1f96db2beb33f955a26a
new file mode 100644
index 000000000..7da4cf5d4
--- /dev/null
+++ b/tests-clar/resources/blametest.git/objects/48/2f2c370e35c2c314fc1f96db2beb33f955a26a
Binary files differ
diff --git a/tests-clar/resources/blametest.git/objects/63/d671eb32d250e4a83766ebbc60e818c1e1e93a b/tests-clar/resources/blametest.git/objects/63/d671eb32d250e4a83766ebbc60e818c1e1e93a
new file mode 100644
index 000000000..5f41f2936
--- /dev/null
+++ b/tests-clar/resources/blametest.git/objects/63/d671eb32d250e4a83766ebbc60e818c1e1e93a
@@ -0,0 +1,3 @@
+xA E]su@bGP뢭Azk<y~60I1S=-6FfC.cp, $b
+8MF
+ᨹO!1eȏ]TqkZV>m)49d-#mgwTK@ \ No newline at end of file
diff --git a/tests-clar/resources/blametest.git/objects/63/eb57322e363e18d460da5ea8284f3cd2340b36 b/tests-clar/resources/blametest.git/objects/63/eb57322e363e18d460da5ea8284f3cd2340b36
new file mode 100644
index 000000000..c6c285eeb
--- /dev/null
+++ b/tests-clar/resources/blametest.git/objects/63/eb57322e363e18d460da5ea8284f3cd2340b36
Binary files differ
diff --git a/tests-clar/resources/blametest.git/objects/8b/137891791fe96927ad78e64b0aad7bded08bdc b/tests-clar/resources/blametest.git/objects/8b/137891791fe96927ad78e64b0aad7bded08bdc
new file mode 100644
index 000000000..9d8f60531
--- /dev/null
+++ b/tests-clar/resources/blametest.git/objects/8b/137891791fe96927ad78e64b0aad7bded08bdc
Binary files differ
diff --git a/tests-clar/resources/blametest.git/objects/96/679d59cf9f74d69b3c920f258559b5e8c9a18a b/tests-clar/resources/blametest.git/objects/96/679d59cf9f74d69b3c920f258559b5e8c9a18a
new file mode 100644
index 000000000..d716e3b03
--- /dev/null
+++ b/tests-clar/resources/blametest.git/objects/96/679d59cf9f74d69b3c920f258559b5e8c9a18a
Binary files differ
diff --git a/tests-clar/resources/blametest.git/objects/98/89d6e5557761aa8e3607e80c874a6dc51ada7c b/tests-clar/resources/blametest.git/objects/98/89d6e5557761aa8e3607e80c874a6dc51ada7c
new file mode 100644
index 000000000..12407f662
--- /dev/null
+++ b/tests-clar/resources/blametest.git/objects/98/89d6e5557761aa8e3607e80c874a6dc51ada7c
Binary files differ
diff --git a/tests-clar/resources/blametest.git/objects/aa/06ecca6c4ad6432ab9313e556ca92ba4bcf9e9 b/tests-clar/resources/blametest.git/objects/aa/06ecca6c4ad6432ab9313e556ca92ba4bcf9e9
new file mode 100644
index 000000000..bc13badb1
--- /dev/null
+++ b/tests-clar/resources/blametest.git/objects/aa/06ecca6c4ad6432ab9313e556ca92ba4bcf9e9
@@ -0,0 +1 @@
+xK0 DYu6I%P9'ǡ]ޟ"NI#<O4YP(O.S y FV/TH^CĘv3r+q-0ZT=\:߀ؠ%t:V忕z.5CA@ \ No newline at end of file
diff --git a/tests-clar/resources/blametest.git/objects/b1/76dfc3a4dc8734e4c579f77236a9c8d0a965d2 b/tests-clar/resources/blametest.git/objects/b1/76dfc3a4dc8734e4c579f77236a9c8d0a965d2
new file mode 100644
index 000000000..caf5cc1e3
--- /dev/null
+++ b/tests-clar/resources/blametest.git/objects/b1/76dfc3a4dc8734e4c579f77236a9c8d0a965d2
Binary files differ
diff --git a/tests-clar/resources/blametest.git/objects/b9/0bb887b7c03750ae6b352ffe76ab9d2e86ee7d b/tests-clar/resources/blametest.git/objects/b9/0bb887b7c03750ae6b352ffe76ab9d2e86ee7d
new file mode 100644
index 000000000..d4f9ccaca
--- /dev/null
+++ b/tests-clar/resources/blametest.git/objects/b9/0bb887b7c03750ae6b352ffe76ab9d2e86ee7d
Binary files differ
diff --git a/tests-clar/resources/blametest.git/objects/b9/9f7ac0b88909253d829554c14af488c3b0f3a5 b/tests-clar/resources/blametest.git/objects/b9/9f7ac0b88909253d829554c14af488c3b0f3a5
new file mode 100644
index 000000000..a428fd63b
--- /dev/null
+++ b/tests-clar/resources/blametest.git/objects/b9/9f7ac0b88909253d829554c14af488c3b0f3a5
@@ -0,0 +1,2 @@
+xK
+1]Ig2Y <A>3L vo{R-e1#B0}s9xR6d1SZx#dj&F #AGK?Kg2r \ No newline at end of file
diff --git a/tests-clar/resources/blametest.git/objects/bc/7c5ac2bafe828a68e9d1d460343718d6fbe136 b/tests-clar/resources/blametest.git/objects/bc/7c5ac2bafe828a68e9d1d460343718d6fbe136
new file mode 100644
index 000000000..4e6ad159e
--- /dev/null
+++ b/tests-clar/resources/blametest.git/objects/bc/7c5ac2bafe828a68e9d1d460343718d6fbe136
@@ -0,0 +1,3 @@
+xAn0 {+t))JA<!/ equl[c*W26t-DSV5C2N#Lᩫ?zdy@7Jc*Y+ nbݧЅ
+)#gJjBH^
+7dMte_׾fd?~-9"1 BPž._!&O \ No newline at end of file
diff --git a/tests-clar/resources/blametest.git/objects/cf/e0e1e1e3ba18f149fd47f5e1aef6016b2260c3 b/tests-clar/resources/blametest.git/objects/cf/e0e1e1e3ba18f149fd47f5e1aef6016b2260c3
new file mode 100644
index 000000000..2048e81eb
--- /dev/null
+++ b/tests-clar/resources/blametest.git/objects/cf/e0e1e1e3ba18f149fd47f5e1aef6016b2260c3
Binary files differ
diff --git a/tests-clar/resources/blametest.git/objects/d0/67729932057cdb7527a833d6799c4ddc520640 b/tests-clar/resources/blametest.git/objects/d0/67729932057cdb7527a833d6799c4ddc520640
new file mode 100644
index 000000000..926c4bbb0
--- /dev/null
+++ b/tests-clar/resources/blametest.git/objects/d0/67729932057cdb7527a833d6799c4ddc520640
@@ -0,0 +1 @@
+x+)JMU03c040031QH+(ayihy>3<^Gn@$H\;MoѬƤ \ No newline at end of file
diff --git a/tests-clar/resources/blametest.git/objects/da/237394e6132d20d30f175b9b73c8638fddddda b/tests-clar/resources/blametest.git/objects/da/237394e6132d20d30f175b9b73c8638fddddda
new file mode 100644
index 000000000..e9e13833f
--- /dev/null
+++ b/tests-clar/resources/blametest.git/objects/da/237394e6132d20d30f175b9b73c8638fddddda
@@ -0,0 +1,4 @@
+xK
+0@]J|'"$vVbz :Sj)ifёDNS dObs[ޱAb(
+Y;f([
+ո%85qK'Y (zF8b@vKc?S \ No newline at end of file
diff --git a/tests-clar/resources/blametest.git/objects/e5/b41c1ea533f87388ab69b13baf0b5a562d6243 b/tests-clar/resources/blametest.git/objects/e5/b41c1ea533f87388ab69b13baf0b5a562d6243
new file mode 100644
index 000000000..7e5586c2b
--- /dev/null
+++ b/tests-clar/resources/blametest.git/objects/e5/b41c1ea533f87388ab69b13baf0b5a562d6243
Binary files differ
diff --git a/tests-clar/resources/blametest.git/objects/ef/32df4d259143933715c74951f932d9892364d1 b/tests-clar/resources/blametest.git/objects/ef/32df4d259143933715c74951f932d9892364d1
new file mode 100644
index 000000000..d021ccfde
--- /dev/null
+++ b/tests-clar/resources/blametest.git/objects/ef/32df4d259143933715c74951f932d9892364d1
Binary files differ
diff --git a/tests-clar/resources/blametest.git/refs/heads/master b/tests-clar/resources/blametest.git/refs/heads/master
new file mode 100644
index 000000000..b763025d8
--- /dev/null
+++ b/tests-clar/resources/blametest.git/refs/heads/master
@@ -0,0 +1 @@
+bc7c5ac2bafe828a68e9d1d460343718d6fbe136