*/
#include "blame_git.h"
+
#include "commit.h"
#include "blob.h"
#include "xdiff/xinclude.h"
+#include "diff_xdiff.h"
/*
* Origin is refcounted and usually we keep the blob contents to be
/* Given a commit and a path in it, create a new origin structure. */
static int make_origin(git_blame__origin **out, git_commit *commit, const char *path)
{
- int error = 0;
git_blame__origin *o;
+ git_object *blob;
+ size_t path_len = strlen(path), alloc_len;
+ int error = 0;
+
+ if ((error = git_object_lookup_bypath(&blob, (git_object*)commit,
+ path, GIT_OBJECT_BLOB)) < 0)
+ return error;
+
+ GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, sizeof(*o), path_len);
+ GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 1);
+ o = git__calloc(1, alloc_len);
+ GIT_ERROR_CHECK_ALLOC(o);
- o = git__calloc(1, sizeof(*o) + strlen(path) + 1);
- GITERR_CHECK_ALLOC(o);
o->commit = commit;
+ o->blob = (git_blob *) blob;
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;
+ *out = o;
+
+ return 0;
}
/* Locate an existing origin or create a new one. */
}
/* find the line number of the last line the target is suspected for */
-static int find_last_in_target(git_blame *blame, git_blame__origin *target)
+static bool find_last_in_target(size_t *out, git_blame *blame, git_blame__origin *target)
{
git_blame__entry *e;
- int last_in_target = -1;
+ size_t last_in_target = 0;
+ bool found = false;
+
+ *out = 0;
for (e=blame->ent; e; e=e->next) {
if (e->guilty || !same_suspect(e->suspect, target))
continue;
- if (last_in_target < e->s_lno + e->num_lines)
+ if (last_in_target < e->s_lno + e->num_lines) {
+ found = true;
last_in_target = e->s_lno + e->num_lines;
+ }
}
- return last_in_target;
+
+ *out = last_in_target;
+ return found;
}
/*
* to be blamed for the parent, and after that portion.
*/
static void split_overlap(git_blame__entry *split, git_blame__entry *e,
- int tlno, int plno, int same, git_blame__origin *parent)
+ size_t tlno, size_t plno, size_t same, git_blame__origin *parent)
{
- int chunk_end_lno;
+ size_t chunk_end_lno;
if (e->s_lno < tlno) {
/* there is a pre-chunk part not blamed on the parent */
* 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(git_blame *blame, git_blame__entry *split, git_blame__entry *e)
+static int split_blame(git_blame *blame, git_blame__entry *split, git_blame__entry *e)
{
git_blame__entry *new_entry;
/* The last part -- me */
new_entry = git__malloc(sizeof(*new_entry));
+ GIT_ERROR_CHECK_ALLOC(new_entry);
memcpy(new_entry, &(split[2]), sizeof(git_blame__entry));
add_blame_entry(blame, new_entry);
/* ... and the middle part -- parent */
new_entry = git__malloc(sizeof(*new_entry));
+ GIT_ERROR_CHECK_ALLOC(new_entry);
memcpy(new_entry, &(split[1]), sizeof(git_blame__entry));
add_blame_entry(blame, new_entry);
} else if (!split[0].suspect && !split[2].suspect) {
/* me and then parent */
dup_entry(e, &split[0]);
new_entry = git__malloc(sizeof(*new_entry));
+ GIT_ERROR_CHECK_ALLOC(new_entry);
memcpy(new_entry, &(split[1]), sizeof(git_blame__entry));
add_blame_entry(blame, new_entry);
} else {
/* parent and then me */
dup_entry(e, &split[1]);
new_entry = git__malloc(sizeof(*new_entry));
+ GIT_ERROR_CHECK_ALLOC(new_entry);
memcpy(new_entry, &(split[2]), sizeof(git_blame__entry));
add_blame_entry(blame, new_entry);
}
+
+ return 0;
}
-/*
+/*
* After splitting the blame, the origins used by the on-stack blame_entry
* should lose one refcnt each.
*/
* 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(
+static int blame_overlap(
git_blame *blame,
git_blame__entry *e,
- int tlno,
- int plno,
- int same,
+ size_t tlno,
+ size_t plno,
+ size_t same,
git_blame__origin *parent)
{
git_blame__entry split[3] = {{0}};
split_overlap(split, e, tlno, plno, same, parent);
if (split[1].suspect)
- split_blame(blame, split, e);
+ if (split_blame(blame, split, e) < 0)
+ return -1;
decref_split(split);
+
+ return 0;
}
/*
* e and its parent. Find and split the overlap, and pass blame to the
* overlapping part to the parent.
*/
-static void blame_chunk(
+static int blame_chunk(
git_blame *blame,
- int tlno,
- int plno,
- int same,
+ size_t tlno,
+ size_t plno,
+ size_t same,
git_blame__origin *target,
git_blame__origin *parent)
{
if (same <= e->s_lno)
continue;
if (tlno < e->s_lno + e->num_lines) {
- blame_overlap(blame, e, tlno, plno, same, parent);
+ if (blame_overlap(blame, e, tlno, plno, same, parent) < 0)
+ return -1;
}
}
+
+ return 0;
}
static int my_emit(
- xdfenv_t *xe,
- xdchange_t *xscr,
- xdemitcb_t *ecb,
- xdemitconf_t const *xecfg)
+ long start_a, long count_a,
+ long start_b, long count_b,
+ void *cb_data)
{
- xdchange_t *xch = xscr;
- GIT_UNUSED(xe);
- GIT_UNUSED(xecfg);
- while (xch) {
- blame_chunk_cb_data *d = ecb->priv;
- blame_chunk(d->blame, d->tlno, d->plno, xch->i2, d->target, d->parent);
- d->plno = xch->i1 + xch->chg1;
- d->tlno = xch->i2 + xch->chg2;
- xch = xch->next;
- }
+ blame_chunk_cb_data *d = (blame_chunk_cb_data *)cb_data;
+
+ if (blame_chunk(d->blame, d->tlno, d->plno, start_b, d->target, d->parent) < 0)
+ return -1;
+ d->plno = start_a + count_a;
+ d->tlno = start_b + count_b;
+
return 0;
}
b->size -= trimmed - recovered;
}
-static int diff_hunks(mmfile_t file_a, mmfile_t file_b, void *cb_data)
+static int diff_hunks(mmfile_t file_a, mmfile_t file_b, void *cb_data, git_blame_options *options)
{
- xpparam_t xpp = {0};
xdemitconf_t xecfg = {0};
xdemitcb_t ecb = {0};
+ xpparam_t xpp = {0};
+
+ if (options->flags & GIT_BLAME_IGNORE_WHITESPACE)
+ xpp.flags |= XDF_IGNORE_WHITESPACE;
- xecfg.emit_func = (void(*)(void))my_emit;
+ xecfg.hunk_func = my_emit;
ecb.priv = cb_data;
trim_common_tail(&file_a, &file_b, 0);
+
+ if (file_a.size > GIT_XDIFF_MAX_SIZE ||
+ file_b.size > GIT_XDIFF_MAX_SIZE) {
+ git_error_set(GIT_ERROR_INVALID, "file too large to blame");
+ return -1;
+ }
+
return xdl_diff(&file_a, &file_b, &xpp, &xecfg, &ecb);
}
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);
+ file->size = (long)git_blob_rawsize(o->blob);
}
}
git_blame__origin *target,
git_blame__origin *parent)
{
- int last_in_target;
+ size_t last_in_target;
mmfile_t file_p, file_o;
blame_chunk_cb_data d = { blame, target, parent, 0, 0 };
- last_in_target = find_last_in_target(blame, target);
- if (last_in_target < 0)
+ if (!find_last_in_target(&last_in_target, blame, target))
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);
+ if (diff_hunks(file_p, file_o, &d, &blame->options) < 0)
+ return -1;
+
/* The reset (i.e. anything after tlno) are the same as the parent */
- blame_chunk(blame, d.tlno, d.plno, last_in_target, target, parent);
+ if (blame_chunk(blame, d.tlno, d.plno, last_in_target, target, parent) < 0)
+ return -1;
return 0;
}
return -1;
}
-static git_blame__origin* find_origin(
+static git_blame__origin *find_origin(
git_blame *blame,
git_commit *parent,
git_blame__origin *origin)
* 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(git_blame *blame,
+static int pass_whole_blame(git_blame *blame,
git_blame__origin *origin, git_blame__origin *porigin)
{
git_blame__entry *e;
- if (!porigin->blob)
- git_object_lookup((git_object**)&porigin->blob, blame->repository,
- git_blob_id(origin->blob), GIT_OBJ_BLOB);
+ if (!porigin->blob &&
+ git_object_lookup((git_object**)&porigin->blob, blame->repository,
+ git_blob_id(origin->blob), GIT_OBJECT_BLOB) < 0)
+ return -1;
for (e=blame->ent; e; e=e->next) {
if (!same_suspect(e->suspect, origin))
continue;
origin_decref(e->suspect);
e->suspect = porigin;
}
+
+ return 0;
}
-static void pass_blame(git_blame *blame, git_blame__origin *origin, uint32_t opt)
+static int pass_blame(git_blame *blame, git_blame__origin *origin, uint32_t opt)
{
git_commit *commit = origin->commit;
int i, num_parents;
git_blame__origin *sg_buf[16];
git_blame__origin *porigin, **sg_origin = sg_buf;
+ int ret, error = 0;
num_parents = git_commit_parentcount(commit);
if (!git_oid_cmp(git_commit_id(commit), &blame->options.oldest_commit))
if (!num_parents) {
git_oid_cpy(&blame->options.oldest_commit, git_commit_id(commit));
goto finish;
- }
- else if (num_parents < (int)ARRAY_SIZE(sg_buf))
+ } else if (num_parents < (int)ARRAY_SIZE(sg_buf))
memset(sg_buf, 0, sizeof(sg_buf));
- else
+ else {
sg_origin = git__calloc(num_parents, sizeof(*sg_origin));
+ GIT_ERROR_CHECK_ALLOC(sg_origin);
+ }
for (i=0; i<num_parents; i++) {
git_commit *p;
if (sg_origin[i])
continue;
- git_commit_parent(&p, origin->commit, i);
+ if ((error = git_commit_parent(&p, origin->commit, i)) < 0)
+ goto finish;
porigin = find_origin(blame, p, origin);
- if (!porigin)
+ if (!porigin) {
+ /*
+ * We only have to decrement the parent's
+ * reference count when no porigin has
+ * been created, as otherwise the commit
+ * is assigned to the created object.
+ */
+ git_commit_free(p);
continue;
+ }
if (porigin->blob && origin->blob &&
!git_oid_cmp(git_blob_id(porigin->blob), git_blob_id(origin->blob))) {
- pass_whole_blame(blame, origin, porigin);
+ error = pass_whole_blame(blame, origin, porigin);
origin_decref(porigin);
goto finish;
}
origin_incref(porigin);
origin->previous = porigin;
}
- if (pass_blame_to_parent(blame, origin, porigin))
+
+ if ((ret = pass_blame_to_parent(blame, origin, porigin)) != 0) {
+ if (ret < 0)
+ error = -1;
+
goto finish;
+ }
}
/* TODO: optionally find moves in parents' files */
origin_decref(sg_origin[i]);
if (sg_origin != sg_buf)
git__free(sg_origin);
- return;
+ return error;
}
/*
}
}
-void git_blame__like_git(git_blame *blame, uint32_t opt)
+int git_blame__like_git(git_blame *blame, uint32_t opt)
{
+ int error = 0;
+
while (true) {
git_blame__entry *ent;
git_blame__origin *suspect = NULL;
if (!ent->guilty)
suspect = ent->suspect;
if (!suspect)
- return; /* all done */
+ break;
/* We'll use this suspect later in the loop, so hold on to it for now. */
origin_incref(suspect);
- pass_blame(blame, suspect, opt);
+
+ if ((error = pass_blame(blame, suspect, opt)) < 0)
+ break;
/* Take responsibility for the remaining entries */
for (ent = blame->ent; ent; ent = ent->next) {
origin_decref(suspect);
}
- coalesce(blame);
+ if (!error)
+ coalesce(blame);
+
+ return error;
}
void git_blame__free_entry(git_blame__entry *ent)