summaryrefslogtreecommitdiff
path: root/builtin/blame.c
diff options
context:
space:
mode:
Diffstat (limited to 'builtin/blame.c')
-rw-r--r--builtin/blame.c2130
1 files changed, 73 insertions, 2057 deletions
diff --git a/builtin/blame.c b/builtin/blame.c
index ca9ebe40e7..67adaef4d8 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -6,21 +6,14 @@
*/
#include "cache.h"
-#include "refs.h"
+#include "config.h"
#include "builtin.h"
-#include "blob.h"
#include "commit.h"
-#include "tag.h"
-#include "tree-walk.h"
#include "diff.h"
-#include "diffcore.h"
#include "revision.h"
#include "quote.h"
-#include "xdiff-interface.h"
-#include "cache-tree.h"
#include "string-list.h"
#include "mailmap.h"
-#include "mergesort.h"
#include "parse-options.h"
#include "prio-queue.h"
#include "utf8.h"
@@ -29,6 +22,7 @@
#include "line-log.h"
#include "dir.h"
#include "progress.h"
+#include "blame.h"
static char blame_usage[] = N_("git blame [<options>] [<rev-opts>] [<rev>] [--] <file>");
@@ -62,1497 +56,21 @@ static struct string_list mailmap = STRING_LIST_INIT_NODUP;
#define DEBUG 0
#endif
-/* stats */
-static int num_read_blob;
-static int num_get_patch;
-static int num_commits;
-
-#define PICKAXE_BLAME_MOVE 01
-#define PICKAXE_BLAME_COPY 02
-#define PICKAXE_BLAME_COPY_HARDER 04
-#define PICKAXE_BLAME_COPY_HARDEST 010
-
-/*
- * blame for a blame_entry with score lower than these thresholds
- * is not passed to the parent using move/copy logic.
- */
static unsigned blame_move_score;
static unsigned blame_copy_score;
-#define BLAME_DEFAULT_MOVE_SCORE 20
-#define BLAME_DEFAULT_COPY_SCORE 40
/* Remember to update object flag allocation in object.h */
#define METAINFO_SHOWN (1u<<12)
#define MORE_THAN_ONE_PATH (1u<<13)
-/*
- * One blob in a commit that is being suspected
- */
-struct origin {
- int refcnt;
- /* Record preceding blame record for this blob */
- struct origin *previous;
- /* origins are put in a list linked via `next' hanging off the
- * corresponding commit's util field in order to make finding
- * them fast. The presence in this chain does not count
- * towards the origin's reference count. It is tempting to
- * let it count as long as the commit is pending examination,
- * but even under circumstances where the commit will be
- * present multiple times in the priority queue of unexamined
- * commits, processing the first instance will not leave any
- * work requiring the origin data for the second instance. An
- * interspersed commit changing that would have to be
- * preexisting with a different ancestry and with the same
- * commit date in order to wedge itself between two instances
- * of the same commit in the priority queue _and_ produce
- * blame entries relevant for it. While we don't want to let
- * us get tripped up by this case, it certainly does not seem
- * worth optimizing for.
- */
- struct origin *next;
- struct commit *commit;
- /* `suspects' contains blame entries that may be attributed to
- * this origin's commit or to parent commits. When a commit
- * is being processed, all suspects will be moved, either by
- * assigning them to an origin in a different commit, or by
- * shipping them to the scoreboard's ent list because they
- * cannot be attributed to a different commit.
- */
- struct blame_entry *suspects;
- mmfile_t file;
- struct object_id blob_oid;
- unsigned mode;
- /* guilty gets set when shipping any suspects to the final
- * blame list instead of other commits
- */
- char guilty;
- char path[FLEX_ARRAY];
-};
-
struct progress_info {
struct progress *progress;
int blamed_lines;
};
-static int diff_hunks(mmfile_t *file_a, mmfile_t *file_b,
- xdl_emit_hunk_consume_func_t hunk_func, void *cb_data)
-{
- xpparam_t xpp = {0};
- xdemitconf_t xecfg = {0};
- xdemitcb_t ecb = {NULL};
-
- xpp.flags = xdl_opts;
- xecfg.hunk_func = hunk_func;
- ecb.priv = cb_data;
- return xdi_diff(file_a, file_b, &xpp, &xecfg, &ecb);
-}
-
-/*
- * Prepare diff_filespec and convert it using diff textconv API
- * if the textconv driver exists.
- * Return 1 if the conversion succeeds, 0 otherwise.
- */
-int textconv_object(const char *path,
- unsigned mode,
- const struct object_id *oid,
- int oid_valid,
- char **buf,
- unsigned long *buf_size)
-{
- struct diff_filespec *df;
- struct userdiff_driver *textconv;
-
- df = alloc_filespec(path);
- fill_filespec(df, oid->hash, oid_valid, mode);
- textconv = get_textconv(df);
- if (!textconv) {
- free_filespec(df);
- return 0;
- }
-
- *buf_size = fill_textconv(textconv, df, buf);
- free_filespec(df);
- return 1;
-}
-
-/*
- * Given an origin, prepare mmfile_t structure to be used by the
- * diff machinery
- */
-static void fill_origin_blob(struct diff_options *opt,
- struct origin *o, mmfile_t *file)
-{
- if (!o->file.ptr) {
- enum object_type type;
- unsigned long file_size;
-
- num_read_blob++;
- if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) &&
- textconv_object(o->path, o->mode, &o->blob_oid, 1, &file->ptr, &file_size))
- ;
- else
- file->ptr = read_sha1_file(o->blob_oid.hash, &type,
- &file_size);
- file->size = file_size;
-
- if (!file->ptr)
- die("Cannot read blob %s for path %s",
- oid_to_hex(&o->blob_oid),
- o->path);
- o->file = *file;
- }
- else
- *file = o->file;
-}
-
-/*
- * Origin is refcounted and usually we keep the blob contents to be
- * reused.
- */
-static inline struct origin *origin_incref(struct origin *o)
-{
- if (o)
- o->refcnt++;
- return o;
-}
-
-static void origin_decref(struct origin *o)
-{
- if (o && --o->refcnt <= 0) {
- struct origin *p, *l = NULL;
- if (o->previous)
- origin_decref(o->previous);
- free(o->file.ptr);
- /* Should be present exactly once in commit chain */
- for (p = o->commit->util; p; l = p, p = p->next) {
- if (p == o) {
- if (l)
- l->next = p->next;
- else
- o->commit->util = p->next;
- free(o);
- return;
- }
- }
- die("internal error in blame::origin_decref");
- }
-}
-
-static void drop_origin_blob(struct origin *o)
-{
- if (o->file.ptr) {
- free(o->file.ptr);
- o->file.ptr = NULL;
- }
-}
-
-/*
- * Each group of lines is described by a blame_entry; it can be split
- * as we pass blame to the parents. They are arranged in linked lists
- * kept as `suspects' of some unprocessed origin, or entered (when the
- * blame origin has been finalized) into the scoreboard structure.
- * While the scoreboard structure is only sorted at the end of
- * processing (according to final image line number), the lists
- * attached to an origin are sorted by the target line number.
- */
-struct blame_entry {
- 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;
-
- /* 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;
-};
-
-/*
- * Any merge of blames happens on lists of blames that arrived via
- * different parents in a single suspect. In this case, we want to
- * sort according to the suspect line numbers as opposed to the final
- * image line numbers. The function body is somewhat longish because
- * it avoids unnecessary writes.
- */
-
-static struct blame_entry *blame_merge(struct blame_entry *list1,
- struct blame_entry *list2)
-{
- struct blame_entry *p1 = list1, *p2 = list2,
- **tail = &list1;
-
- if (!p1)
- return p2;
- if (!p2)
- return p1;
-
- if (p1->s_lno <= p2->s_lno) {
- do {
- tail = &p1->next;
- if ((p1 = *tail) == NULL) {
- *tail = p2;
- return list1;
- }
- } while (p1->s_lno <= p2->s_lno);
- }
- for (;;) {
- *tail = p2;
- do {
- tail = &p2->next;
- if ((p2 = *tail) == NULL) {
- *tail = p1;
- return list1;
- }
- } while (p1->s_lno > p2->s_lno);
- *tail = p1;
- do {
- tail = &p1->next;
- if ((p1 = *tail) == NULL) {
- *tail = p2;
- return list1;
- }
- } while (p1->s_lno <= p2->s_lno);
- }
-}
-
-static void *get_next_blame(const void *p)
-{
- return ((struct blame_entry *)p)->next;
-}
-
-static void set_next_blame(void *p1, void *p2)
-{
- ((struct blame_entry *)p1)->next = p2;
-}
-
-/*
- * Final image line numbers are all different, so we don't need a
- * three-way comparison here.
- */
-
-static int compare_blame_final(const void *p1, const void *p2)
-{
- return ((struct blame_entry *)p1)->lno > ((struct blame_entry *)p2)->lno
- ? 1 : -1;
-}
-
-static int compare_blame_suspect(const void *p1, const void *p2)
-{
- const struct blame_entry *s1 = p1, *s2 = p2;
- /*
- * to allow for collating suspects, we sort according to the
- * respective pointer value as the primary sorting criterion.
- * The actual relation is pretty unimportant as long as it
- * establishes a total order. Comparing as integers gives us
- * that.
- */
- if (s1->suspect != s2->suspect)
- return (intptr_t)s1->suspect > (intptr_t)s2->suspect ? 1 : -1;
- if (s1->s_lno == s2->s_lno)
- return 0;
- return s1->s_lno > s2->s_lno ? 1 : -1;
-}
-
-static struct blame_entry *blame_sort(struct blame_entry *head,
- int (*compare_fn)(const void *, const void *))
-{
- return llist_mergesort (head, get_next_blame, set_next_blame, compare_fn);
-}
-
-static int compare_commits_by_reverse_commit_date(const void *a,
- const void *b,
- void *c)
-{
- return -compare_commits_by_commit_date(a, b, c);
-}
-
-/*
- * The current state of the blame assignment.
- */
-struct scoreboard {
- /* the final commit (i.e. where we started digging from) */
- struct commit *final;
- /* Priority queue for commits with unassigned blame records */
- struct prio_queue commits;
- struct rev_info *revs;
- 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;
- unsigned long final_buf_size;
-
- /* linked list of blames */
- struct blame_entry *ent;
-
- /* look-up a line in the final buffer */
- int num_lines;
- int *lineno;
-};
-
-static void sanity_check_refcnt(struct scoreboard *);
-
-/*
- * If two blame entries that are next to each other came from
- * contiguous lines in the same origin (i.e. <commit, path> pair),
- * merge them together.
- */
-static void coalesce(struct scoreboard *sb)
-{
- struct blame_entry *ent, *next;
-
- for (ent = sb->ent; ent && (next = ent->next); ent = next) {
- if (ent->suspect == next->suspect &&
- ent->s_lno + ent->num_lines == next->s_lno) {
- ent->num_lines += next->num_lines;
- ent->next = next->next;
- origin_decref(next->suspect);
- free(next);
- ent->score = 0;
- next = ent; /* again */
- }
- }
-
- if (DEBUG) /* sanity */
- sanity_check_refcnt(sb);
-}
-
-/*
- * Merge the given sorted list of blames into a preexisting origin.
- * If there were no previous blames to that commit, it is entered into
- * the commit priority queue of the score board.
- */
-
-static void queue_blames(struct scoreboard *sb, struct origin *porigin,
- struct blame_entry *sorted)
-{
- if (porigin->suspects)
- porigin->suspects = blame_merge(porigin->suspects, sorted);
- else {
- struct origin *o;
- for (o = porigin->commit->util; o; o = o->next) {
- if (o->suspects) {
- porigin->suspects = sorted;
- return;
- }
- }
- porigin->suspects = sorted;
- prio_queue_put(&sb->commits, porigin->commit);
- }
-}
-
-/*
- * 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.
- */
-static struct origin *make_origin(struct commit *commit, const char *path)
-{
- struct origin *o;
- FLEX_ALLOC_STR(o, path, path);
- o->commit = commit;
- o->refcnt = 1;
- o->next = commit->util;
- commit->util = o;
- return o;
-}
-
-/*
- * Locate an existing origin or create a new one.
- * This moves the origin to front position in the commit util list.
- */
-static struct origin *get_origin(struct scoreboard *sb,
- struct commit *commit,
- const char *path)
-{
- struct origin *o, *l;
-
- for (o = commit->util, l = NULL; o; l = o, o = o->next) {
- if (!strcmp(o->path, path)) {
- /* bump to front */
- if (l) {
- l->next = o->next;
- o->next = commit->util;
- commit->util = o;
- }
- return origin_incref(o);
- }
- }
- return make_origin(commit, path);
-}
-
-/*
- * Fill the blob_sha1 field of an origin if it hasn't, so that later
- * call to fill_origin_blob() can use it to locate the data. blob_sha1
- * for an origin is also used to pass the blame for the entire file to
- * the parent to detect the case where a child's blob is identical to
- * that of its parent's.
- *
- * This also fills origin->mode for corresponding tree path.
- */
-static int fill_blob_sha1_and_mode(struct origin *origin)
-{
- if (!is_null_oid(&origin->blob_oid))
- return 0;
- if (get_tree_entry(origin->commit->object.oid.hash,
- origin->path,
- origin->blob_oid.hash, &origin->mode))
- goto error_out;
- if (sha1_object_info(origin->blob_oid.hash, NULL) != OBJ_BLOB)
- goto error_out;
- return 0;
- error_out:
- oidclr(&origin->blob_oid);
- origin->mode = S_IFINVALID;
- return -1;
-}
-
-/*
- * We have an origin -- check if the same path exists in the
- * parent and return an origin structure to represent it.
- */
-static struct origin *find_origin(struct scoreboard *sb,
- struct commit *parent,
- struct origin *origin)
-{
- struct origin *porigin;
- struct diff_options diff_opts;
- const char *paths[2];
-
- /* First check any existing origins */
- for (porigin = parent->util; porigin; porigin = porigin->next)
- if (!strcmp(porigin->path, origin->path)) {
- /*
- * The same path between origin and its parent
- * without renaming -- the most common case.
- */
- return origin_incref (porigin);
- }
-
- /* See if the origin->path is different between parent
- * and origin first. Most of the time they are the
- * same and diff-tree is fairly efficient about this.
- */
- diff_setup(&diff_opts);
- DIFF_OPT_SET(&diff_opts, RECURSIVE);
- diff_opts.detect_rename = 0;
- diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
- paths[0] = origin->path;
- paths[1] = NULL;
-
- parse_pathspec(&diff_opts.pathspec,
- PATHSPEC_ALL_MAGIC & ~PATHSPEC_LITERAL,
- PATHSPEC_LITERAL_PATH, "", paths);
- diff_setup_done(&diff_opts);
-
- if (is_null_oid(&origin->commit->object.oid))
- do_diff_cache(parent->tree->object.oid.hash, &diff_opts);
- else
- diff_tree_sha1(parent->tree->object.oid.hash,
- origin->commit->tree->object.oid.hash,
- "", &diff_opts);
- diffcore_std(&diff_opts);
-
- if (!diff_queued_diff.nr) {
- /* The path is the same as parent */
- porigin = get_origin(sb, parent, origin->path);
- oidcpy(&porigin->blob_oid, &origin->blob_oid);
- porigin->mode = origin->mode;
- } else {
- /*
- * Since origin->path is a pathspec, if the parent
- * commit had it as a directory, we will see a whole
- * bunch of deletion of files in the directory that we
- * do not care about.
- */
- int i;
- struct diff_filepair *p = NULL;
- for (i = 0; i < diff_queued_diff.nr; i++) {
- const char *name;
- p = diff_queued_diff.queue[i];
- name = p->one->path ? p->one->path : p->two->path;
- if (!strcmp(name, origin->path))
- break;
- }
- if (!p)
- die("internal error in blame::find_origin");
- switch (p->status) {
- default:
- die("internal error in blame::find_origin (%c)",
- p->status);
- case 'M':
- porigin = get_origin(sb, parent, origin->path);
- oidcpy(&porigin->blob_oid, &p->one->oid);
- porigin->mode = p->one->mode;
- break;
- case 'A':
- case 'T':
- /* Did not exist in parent, or type changed */
- break;
- }
- }
- diff_flush(&diff_opts);
- clear_pathspec(&diff_opts.pathspec);
- return porigin;
-}
-
-/*
- * We have an origin -- find the path that corresponds to it in its
- * parent and return an origin structure to represent it.
- */
-static struct origin *find_rename(struct scoreboard *sb,
- struct commit *parent,
- struct origin *origin)
-{
- struct origin *porigin = NULL;
- struct diff_options diff_opts;
- int i;
-
- diff_setup(&diff_opts);
- DIFF_OPT_SET(&diff_opts, RECURSIVE);
- diff_opts.detect_rename = DIFF_DETECT_RENAME;
- diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
- diff_opts.single_follow = origin->path;
- diff_setup_done(&diff_opts);
-
- if (is_null_oid(&origin->commit->object.oid))
- do_diff_cache(parent->tree->object.oid.hash, &diff_opts);
- else
- diff_tree_sha1(parent->tree->object.oid.hash,
- origin->commit->tree->object.oid.hash,
- "", &diff_opts);
- diffcore_std(&diff_opts);
-
- for (i = 0; i < diff_queued_diff.nr; i++) {
- struct diff_filepair *p = diff_queued_diff.queue[i];
- if ((p->status == 'R' || p->status == 'C') &&
- !strcmp(p->two->path, origin->path)) {
- porigin = get_origin(sb, parent, p->one->path);
- oidcpy(&porigin->blob_oid, &p->one->oid);
- porigin->mode = p->one->mode;
- break;
- }
- }
- diff_flush(&diff_opts);
- clear_pathspec(&diff_opts.pathspec);
- return porigin;
-}
-
-/*
- * Append a new blame entry to a given output queue.
- */
-static void add_blame_entry(struct blame_entry ***queue,
- const struct blame_entry *src)
-{
- struct blame_entry *e = xmalloc(sizeof(*e));
- memcpy(e, src, sizeof(*e));
- origin_incref(e->suspect);
-
- e->next = **queue;
- **queue = e;
- *queue = &e->next;
-}
-
-/*
- * src typically is on-stack; we want to copy the information in it to
- * a malloced blame_entry that gets added to the given queue. The
- * origin of dst loses a refcnt.
- */
-static void dup_entry(struct blame_entry ***queue,
- struct blame_entry *dst, struct blame_entry *src)
-{
- origin_incref(src->suspect);
- origin_decref(dst->suspect);
- memcpy(dst, src, sizeof(*src));
- dst->next = **queue;
- **queue = dst;
- *queue = &dst->next;
-}
-
-static const char *nth_line(struct scoreboard *sb, long lno)
-{
- return sb->final_buf + sb->lineno[lno];
-}
-
static const char *nth_line_cb(void *data, long lno)
{
- return nth_line((struct scoreboard *)data, lno);
-}
-
-/*
- * 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;
- memset(split, 0, sizeof(struct blame_entry [3]));
-
- if (e->s_lno < tlno) {
- /* there is a pre-chunk part not blamed on 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);
-}
-
-/*
- * split_overlap() divided an existing blame e into up to three parts
- * in split. Any assigned blame is moved to queue to
- * reflect the split.
- */
-static void split_blame(struct blame_entry ***blamed,
- struct blame_entry ***unblamed,
- struct blame_entry *split,
- struct blame_entry *e)
-{
- if (split[0].suspect && split[2].suspect) {
- /* The first part (reuse storage for the existing entry e) */
- dup_entry(unblamed, e, &split[0]);
-
- /* The last part -- me */
- add_blame_entry(unblamed, &split[2]);
-
- /* ... and the middle part -- parent */
- add_blame_entry(blamed, &split[1]);
- }
- 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(blamed, e, &split[1]);
- else if (split[0].suspect) {
- /* me and then parent */
- dup_entry(unblamed, e, &split[0]);
- add_blame_entry(blamed, &split[1]);
- }
- else {
- /* parent and then me */
- dup_entry(blamed, e, &split[1]);
- add_blame_entry(unblamed, &split[2]);
- }
-}
-
-/*
- * 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);
-}
-
-/*
- * reverse_blame reverses the list given in head, appending tail.
- * That allows us to build lists in reverse order, then reverse them
- * afterwards. This can be faster than building the list in proper
- * order right away. The reason is that building in proper order
- * requires writing a link in the _previous_ element, while building
- * in reverse order just requires placing the list head into the
- * _current_ element.
- */
-
-static struct blame_entry *reverse_blame(struct blame_entry *head,
- struct blame_entry *tail)
-{
- while (head) {
- struct blame_entry *next = head->next;
- head->next = tail;
- tail = head;
- head = next;
- }
- return tail;
-}
-
-/*
- * Process one hunk from the patch between the current suspect for
- * blame_entry e and its parent. This first blames any unfinished
- * entries before the chunk (which is where target and parent start
- * differing) on the parent, and then splits blame entries at the
- * start and at the end of the difference region. Since use of -M and
- * -C options may lead to overlapping/duplicate source line number
- * ranges, all we can rely on from sorting/merging is the order of the
- * first suspect line number.
- */
-static void blame_chunk(struct blame_entry ***dstq, struct blame_entry ***srcq,
- int tlno, int offset, int same,
- struct origin *parent)
-{
- struct blame_entry *e = **srcq;
- struct blame_entry *samep = NULL, *diffp = NULL;
-
- while (e && e->s_lno < tlno) {
- struct blame_entry *next = e->next;
- /*
- * current record starts before differing portion. If
- * it reaches into it, we need to split it up and
- * examine the second part separately.
- */
- if (e->s_lno + e->num_lines > tlno) {
- /* Move second half to a new record */
- int len = tlno - e->s_lno;
- struct blame_entry *n = xcalloc(1, sizeof (struct blame_entry));
- n->suspect = e->suspect;
- n->lno = e->lno + len;
- n->s_lno = e->s_lno + len;
- n->num_lines = e->num_lines - len;
- e->num_lines = len;
- e->score = 0;
- /* Push new record to diffp */
- n->next = diffp;
- diffp = n;
- } else
- origin_decref(e->suspect);
- /* Pass blame for everything before the differing
- * chunk to the parent */
- e->suspect = origin_incref(parent);
- e->s_lno += offset;
- e->next = samep;
- samep = e;
- e = next;
- }
- /*
- * As we don't know how much of a common stretch after this
- * diff will occur, the currently blamed parts are all that we
- * can assign to the parent for now.
- */
-
- if (samep) {
- **dstq = reverse_blame(samep, **dstq);
- *dstq = &samep->next;
- }
- /*
- * Prepend the split off portions: everything after e starts
- * after the blameable portion.
- */
- e = reverse_blame(diffp, e);
-
- /*
- * Now retain records on the target while parts are different
- * from the parent.
- */
- samep = NULL;
- diffp = NULL;
- while (e && e->s_lno < same) {
- struct blame_entry *next = e->next;
-
- /*
- * If current record extends into sameness, need to split.
- */
- if (e->s_lno + e->num_lines > same) {
- /*
- * Move second half to a new record to be
- * processed by later chunks
- */
- int len = same - e->s_lno;
- struct blame_entry *n = xcalloc(1, sizeof (struct blame_entry));
- n->suspect = origin_incref(e->suspect);
- n->lno = e->lno + len;
- n->s_lno = e->s_lno + len;
- n->num_lines = e->num_lines - len;
- e->num_lines = len;
- e->score = 0;
- /* Push new record to samep */
- n->next = samep;
- samep = n;
- }
- e->next = diffp;
- diffp = e;
- e = next;
- }
- **srcq = reverse_blame(diffp, reverse_blame(samep, e));
- /* Move across elements that are in the unblamable portion */
- if (diffp)
- *srcq = &diffp->next;
-}
-
-struct blame_chunk_cb_data {
- struct origin *parent;
- long offset;
- struct blame_entry **dstq;
- struct blame_entry **srcq;
-};
-
-/* diff chunks are from parent to target */
-static int blame_chunk_cb(long start_a, long count_a,
- long start_b, long count_b, void *data)
-{
- struct blame_chunk_cb_data *d = data;
- if (start_a - start_b != d->offset)
- die("internal error in blame::blame_chunk_cb");
- blame_chunk(&d->dstq, &d->srcq, start_b, start_a - start_b,
- start_b + count_b, d->parent);
- d->offset = start_a + count_a - (start_b + count_b);
- return 0;
-}
-
-/*
- * We are looking at the origin 'target' and aiming to pass blame
- * for the lines it is suspected to its parent. Run diff to find
- * which lines came from parent and pass blame for them.
- */
-static void pass_blame_to_parent(struct scoreboard *sb,
- struct origin *target,
- struct origin *parent)
-{
- mmfile_t file_p, file_o;
- struct blame_chunk_cb_data d;
- struct blame_entry *newdest = NULL;
-
- if (!target->suspects)
- return; /* nothing remains for this target */
-
- d.parent = parent;
- d.offset = 0;
- d.dstq = &newdest; d.srcq = &target->suspects;
-
- fill_origin_blob(&sb->revs->diffopt, parent, &file_p);
- fill_origin_blob(&sb->revs->diffopt, target, &file_o);
- num_get_patch++;
-
- if (diff_hunks(&file_p, &file_o, blame_chunk_cb, &d))
- die("unable to generate diff (%s -> %s)",
- oid_to_hex(&parent->commit->object.oid),
- oid_to_hex(&target->commit->object.oid));
- /* The rest are the same as the parent */
- blame_chunk(&d.dstq, &d.srcq, INT_MAX, d.offset, INT_MAX, parent);
- *d.dstq = NULL;
- queue_blames(sb, parent, newdest);
-
- return;
-}
-
-/*
- * The lines in blame_entry after splitting blames many times can become
- * very small and trivial, and at some point it becomes pointless to
- * blame the parents. E.g. "\t\t}\n\t}\n\n" appears everywhere in any
- * ordinary C program, and it is not worth to say it was copied from
- * totally unrelated file in the parent.
- *
- * Compute how trivial the lines in the blame_entry are.
- */
-static unsigned ent_score(struct scoreboard *sb, struct blame_entry *e)
-{
- unsigned score;
- const char *cp, *ep;
-
- if (e->score)
- return e->score;
-
- score = 1;
- cp = nth_line(sb, e->lno);
- ep = nth_line(sb, e->lno + e->num_lines);
- while (cp < ep) {
- unsigned ch = *((unsigned char *)cp);
- if (isalnum(ch))
- score++;
- cp++;
- }
- e->score = score;
- return score;
-}
-
-/*
- * best_so_far[] and this[] are both a split of an existing blame_entry
- * that passes blame to the parent. Maintain best_so_far the best split
- * so far, by comparing this and best_so_far and copying this into
- * bst_so_far as needed.
- */
-static void copy_split_if_better(struct scoreboard *sb,
- struct blame_entry *best_so_far,
- struct blame_entry *this)
-{
- int i;
-
- if (!this[1].suspect)
- return;
- if (best_so_far[1].suspect) {
- if (ent_score(sb, &this[1]) < ent_score(sb, &best_so_far[1]))
- return;
- }
-
- for (i = 0; i < 3; i++)
- origin_incref(this[i].suspect);
- decref_split(best_so_far);
- memcpy(best_so_far, this, sizeof(struct blame_entry [3]));
-}
-
-/*
- * We are looking at a part of the final image represented by
- * ent (tlno and same are offset by ent->s_lno).
- * tlno is where we are looking at in the final image.
- * up to (but not including) same match preimage.
- * plno is where we are looking at in the preimage.
- *
- * <-------------- final image ---------------------->
- * <------ent------>
- * ^tlno ^same
- * <---------preimage----->
- * ^plno
- *
- * All line numbers are 0-based.
- */
-static void handle_split(struct scoreboard *sb,
- struct blame_entry *ent,
- int tlno, int plno, int same,
- struct origin *parent,
- struct blame_entry *split)
-{
- if (ent->num_lines <= tlno)
- return;
- if (tlno < same) {
- struct blame_entry this[3];
- tlno += ent->s_lno;
- same += ent->s_lno;
- split_overlap(this, ent, tlno, plno, same, parent);
- copy_split_if_better(sb, split, this);
- decref_split(this);
- }
-}
-
-struct handle_split_cb_data {
- struct scoreboard *sb;
- struct blame_entry *ent;
- struct origin *parent;
- struct blame_entry *split;
- long plno;
- long tlno;
-};
-
-static int handle_split_cb(long start_a, long count_a,
- long start_b, long count_b, void *data)
-{
- struct handle_split_cb_data *d = data;
- handle_split(d->sb, d->ent, d->tlno, d->plno, start_b, d->parent,
- d->split);
- d->plno = start_a + count_a;
- d->tlno = start_b + count_b;
- return 0;
-}
-
-/*
- * Find the lines from parent that are the same as ent so that
- * we can pass blames to it. file_p has the blob contents for
- * the parent.
- */
-static void find_copy_in_blob(struct scoreboard *sb,
- struct blame_entry *ent,
- struct origin *parent,
- struct blame_entry *split,
- mmfile_t *file_p)
-{
- const char *cp;
- mmfile_t file_o;
- struct handle_split_cb_data d;
-
- memset(&d, 0, sizeof(d));
- d.sb = sb; d.ent = ent; d.parent = parent; d.split = split;
- /*
- * Prepare mmfile that contains only the lines in ent.
- */
- cp = nth_line(sb, ent->lno);
- file_o.ptr = (char *) cp;
- file_o.size = nth_line(sb, ent->lno + ent->num_lines) - cp;
-
- /*
- * file_o is a part of final image we are annotating.
- * file_p partially may match that image.
- */
- memset(split, 0, sizeof(struct blame_entry [3]));
- if (diff_hunks(file_p, &file_o, handle_split_cb, &d))
- die("unable to generate diff (%s)",
- oid_to_hex(&parent->commit->object.oid));
- /* remainder, if any, all match the preimage */
- handle_split(sb, ent, d.tlno, d.plno, ent->num_lines, parent, split);
-}
-
-/* Move all blame entries from list *source that have a score smaller
- * than score_min to the front of list *small.
- * Returns a pointer to the link pointing to the old head of the small list.
- */
-
-static struct blame_entry **filter_small(struct scoreboard *sb,
- struct blame_entry **small,
- struct blame_entry **source,
- unsigned score_min)
-{
- struct blame_entry *p = *source;
- struct blame_entry *oldsmall = *small;
- while (p) {
- if (ent_score(sb, p) <= score_min) {
- *small = p;
- small = &p->next;
- p = *small;
- } else {
- *source = p;
- source = &p->next;
- p = *source;
- }
- }
- *small = oldsmall;
- *source = NULL;
- return small;
-}
-
-/*
- * See if lines currently target is suspected for can be attributed to
- * parent.
- */
-static void find_move_in_parent(struct scoreboard *sb,
- struct blame_entry ***blamed,
- struct blame_entry **toosmall,
- struct origin *target,
- struct origin *parent)
-{
- struct blame_entry *e, split[3];
- struct blame_entry *unblamed = target->suspects;
- struct blame_entry *leftover = NULL;
- mmfile_t file_p;
-
- if (!unblamed)
- return; /* nothing remains for this target */
-
- fill_origin_blob(&sb->revs->diffopt, parent, &file_p);
- if (!file_p.ptr)
- return;
-
- /* At each iteration, unblamed has a NULL-terminated list of
- * entries that have not yet been tested for blame. leftover
- * contains the reversed list of entries that have been tested
- * without being assignable to the parent.
- */
- do {
- struct blame_entry **unblamedtail = &unblamed;
- struct blame_entry *next;
- for (e = unblamed; e; e = next) {
- next = e->next;
- find_copy_in_blob(sb, e, parent, split, &file_p);
- if (split[1].suspect &&
- blame_move_score < ent_score(sb, &split[1])) {