/* Distribute collected unsorted blames to the respected sorted lists * in the various origins. */ static void distribute_blame(struct blame_scoreboard *sb, struct blame_entry *blamed) { blamed = llist_mergesort(blamed, get_next_blame, set_next_blame, compare_blame_suspect); while (blamed) { struct blame_origin *porigin = blamed->suspect; struct blame_entry *suspects = NULL; do { struct blame_entry *next = blamed->next; blamed->next = suspects; suspects = blamed; blamed = next; } while (blamed && blamed->suspect == porigin); suspects = reverse_blame(suspects, NULL); queue_blames(sb, porigin, suspects); } }
/* * See if lines currently target is suspected for can be attributed to * parent. */ static void find_move_in_parent(struct blame_scoreboard *sb, struct blame_entry ***blamed, struct blame_entry **toosmall, struct blame_origin *target, struct blame_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, &sb->num_read_blob); 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 && sb->move_score < blame_entry_score(sb, &split[1])) { split_blame(blamed, &unblamedtail, split, e); } else { e->next = leftover; leftover = e; } decref_split(split); } *unblamedtail = NULL; toosmall = filter_small(sb, toosmall, &unblamed, sb->move_score); } while (unblamed); target->suspects = reverse_blame(leftover, NULL); }
/* * 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 blame_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 blame_origin_decref(e->suspect); /* Pass blame for everything before the differing * chunk to the parent */ e->suspect = blame_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 = blame_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; }
/* * For lines target is suspected for, see if we can find code movement * across file boundary from the parent commit. porigin is the path * in the parent we already tried. */ static void find_copy_in_parent(struct blame_scoreboard *sb, struct blame_entry ***blamed, struct blame_entry **toosmall, struct blame_origin *target, struct commit *parent, struct blame_origin *porigin, int opt) { struct diff_options diff_opts; int i, j; struct blame_list *blame_list; int num_ents; struct blame_entry *unblamed = target->suspects; struct blame_entry *leftover = NULL; if (!unblamed) return; /* nothing remains for this target */ diff_setup(&diff_opts); diff_opts.flags.recursive = 1; diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; diff_setup_done(&diff_opts); /* Try "find copies harder" on new path if requested; * we do not want to use diffcore_rename() actually to * match things up; find_copies_harder is set only to * force diff_tree_oid() to feed all filepairs to diff_queue, * and this code needs to be after diff_setup_done(), which * usually makes find-copies-harder imply copy detection. */ if ((opt & PICKAXE_BLAME_COPY_HARDEST) || ((opt & PICKAXE_BLAME_COPY_HARDER) && (!porigin || strcmp(target->path, porigin->path)))) diff_opts.flags.find_copies_harder = 1; if (is_null_oid(&target->commit->object.oid)) do_diff_cache(get_commit_tree_oid(parent), &diff_opts); else diff_tree_oid(get_commit_tree_oid(parent), get_commit_tree_oid(target->commit), "", &diff_opts); if (!diff_opts.flags.find_copies_harder) diffcore_std(&diff_opts); do { struct blame_entry **unblamedtail = &unblamed; blame_list = setup_blame_list(unblamed, &num_ents); for (i = 0; i < diff_queued_diff.nr; i++) { struct diff_filepair *p = diff_queued_diff.queue[i]; struct blame_origin *norigin; mmfile_t file_p; struct blame_entry potential[3]; if (!DIFF_FILE_VALID(p->one)) continue; /* does not exist in parent */ if (S_ISGITLINK(p->one->mode)) continue; /* ignore git links */ if (porigin && !strcmp(p->one->path, porigin->path)) /* find_move already dealt with this path */ continue; norigin = get_origin(parent, p->one->path); oidcpy(&norigin->blob_oid, &p->one->oid); norigin->mode = p->one->mode; fill_origin_blob(&sb->revs->diffopt, norigin, &file_p, &sb->num_read_blob); if (!file_p.ptr) continue; for (j = 0; j < num_ents; j++) { find_copy_in_blob(sb, blame_list[j].ent, norigin, potential, &file_p); copy_split_if_better(sb, blame_list[j].split, potential); decref_split(potential); } blame_origin_decref(norigin); } for (j = 0; j < num_ents; j++) { struct blame_entry *split = blame_list[j].split; if (split[1].suspect && sb->copy_score < blame_entry_score(sb, &split[1])) { split_blame(blamed, &unblamedtail, split, blame_list[j].ent); } else { blame_list[j].ent->next = leftover; leftover = blame_list[j].ent; } decref_split(split); } free(blame_list); *unblamedtail = NULL; toosmall = filter_small(sb, toosmall, &unblamed, sb->copy_score); } while (unblamed); target->suspects = reverse_blame(leftover, NULL); diff_flush(&diff_opts); clear_pathspec(&diff_opts.pathspec); }