]> git.proxmox.com Git - libgit2.git/blobdiff - src/blame_git.c
New upstream version 1.4.3+dfsg.1
[libgit2.git] / src / blame_git.c
index f481426aa8541928ff9dd4cdfd45a41fed578af7..2504b338ac595fcf04b361dfceac5587536dcdca 100644 (file)
@@ -6,9 +6,11 @@
  */
 
 #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
@@ -36,25 +38,27 @@ static void origin_decref(git_blame__origin *o)
 static int make_origin(git_blame__origin **out, git_commit *commit, const char *path)
 {
        git_blame__origin *o;
+       git_object *blob;
        size_t path_len = strlen(path), alloc_len;
        int error = 0;
 
-       GITERR_CHECK_ALLOC_ADD(&alloc_len, sizeof(*o), path_len);
-       GITERR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 1);
+       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);
-       GITERR_CHECK_ALLOC(o);
+       GIT_ERROR_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. */
@@ -92,18 +96,25 @@ static bool same_suspect(git_blame__origin *a, git_blame__origin *b)
 }
 
 /* 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;
 }
 
 /*
@@ -121,9 +132,9 @@ static int find_last_in_target(git_blame *blame, git_blame__origin *target)
  * 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 */
@@ -208,7 +219,7 @@ static void dup_entry(git_blame__entry *dst, git_blame__entry *src)
  * 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;
 
@@ -218,11 +229,13 @@ static void split_blame(git_blame *blame, git_blame__entry *split, git_blame__en
 
                /* 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) {
@@ -235,18 +248,22 @@ static void split_blame(git_blame *blame, git_blame__entry *split, git_blame__en
                /* 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.
  */
@@ -261,20 +278,23 @@ static void decref_split(git_blame__entry *split)
  * 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;
 }
 
 /*
@@ -282,11 +302,11 @@ static void blame_overlap(
  * 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)
 {
@@ -298,9 +318,12 @@ static void blame_chunk(
                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(
@@ -310,10 +333,11 @@ static int my_emit(
 {
        blame_chunk_cb_data *d = (blame_chunk_cb_data *)cb_data;
 
-       blame_chunk(d->blame, d->tlno, d->plno, start_b, d->target, d->parent);
+       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;
 }
 
@@ -341,16 +365,26 @@ static void trim_common_tail(mmfile_t *a, mmfile_t *b, long ctx)
        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.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);
 }
 
@@ -359,7 +393,7 @@ static void fill_origin_blob(git_blame__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);
+               file->size = (long)git_blob_rawsize(o->blob);
        }
 }
 
@@ -368,20 +402,22 @@ static int pass_blame_to_parent(
                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;
 }
@@ -393,7 +429,7 @@ static int paths_on_dup(void **old, void *new)
        return -1;
 }
 
-static git_blame__originfind_origin(
+static git_blame__origin *find_origin(
                git_blame *blame,
                git_commit *parent,
                git_blame__origin *origin)
@@ -460,14 +496,15 @@ cleanup:
  * 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;
@@ -475,14 +512,17 @@ static void pass_whole_blame(git_blame *blame,
                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))
@@ -495,11 +535,12 @@ static void pass_blame(git_blame *blame, git_blame__origin *origin, uint32_t opt
        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;
@@ -508,14 +549,23 @@ static void pass_blame(git_blame *blame, git_blame__origin *origin, uint32_t opt
                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;
                }
@@ -540,8 +590,13 @@ static void pass_blame(git_blame *blame, git_blame__origin *origin, uint32_t opt
                        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 */
@@ -554,7 +609,7 @@ finish:
                        origin_decref(sg_origin[i]);
        if (sg_origin != sg_buf)
                git__free(sg_origin);
-       return;
+       return error;
 }
 
 /*
@@ -583,8 +638,10 @@ static void coalesce(git_blame *blame)
        }
 }
 
-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;
@@ -594,11 +651,13 @@ void git_blame__like_git(git_blame *blame, uint32_t opt)
                        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) {
@@ -612,7 +671,10 @@ void git_blame__like_git(git_blame *blame, uint32_t opt)
                origin_decref(suspect);
        }
 
-       coalesce(blame);
+       if (!error)
+               coalesce(blame);
+
+       return error;
 }
 
 void git_blame__free_entry(git_blame__entry *ent)