Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.kernel.org/pub/scm/git/git.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'blame.c')
-rw-r--r--blame.c176
1 files changed, 167 insertions, 9 deletions
diff --git a/blame.c b/blame.c
index b6f8ba75f9..c57d7a3632 100644
--- a/blame.c
+++ b/blame.c
@@ -859,6 +859,103 @@ static struct blame_entry *split_blame_at(struct blame_entry *e, int len,
return n;
}
+struct blame_line_tracker {
+ int is_parent;
+ int s_lno;
+};
+
+static int are_lines_adjacent(struct blame_line_tracker *first,
+ struct blame_line_tracker *second)
+{
+ return first->is_parent == second->is_parent &&
+ first->s_lno + 1 == second->s_lno;
+}
+
+/*
+ * This cheap heuristic assigns lines in the chunk to their relative location in
+ * the parent's chunk. Any additional lines are left with the target.
+ */
+static void guess_line_blames(struct blame_origin *parent,
+ struct blame_origin *target,
+ int tlno, int offset, int same, int parent_len,
+ struct blame_line_tracker *line_blames)
+{
+ int i, best_idx, target_idx;
+ int parent_slno = tlno + offset;
+
+ for (i = 0; i < same - tlno; i++) {
+ target_idx = tlno + i;
+ best_idx = target_idx + offset;
+ if (best_idx < parent_slno + parent_len) {
+ line_blames[i].is_parent = 1;
+ line_blames[i].s_lno = best_idx;
+ } else {
+ line_blames[i].is_parent = 0;
+ line_blames[i].s_lno = target_idx;
+ }
+ }
+}
+
+/*
+ * This decides which parts of a blame entry go to the parent (added to the
+ * ignoredp list) and which stay with the target (added to the diffp list). The
+ * actual decision was made in a separate heuristic function, and those answers
+ * for the lines in 'e' are in line_blames. This consumes e, essentially
+ * putting it on a list.
+ *
+ * Note that the blame entries on the ignoredp list are not necessarily sorted
+ * with respect to the parent's line numbers yet.
+ */
+static void ignore_blame_entry(struct blame_entry *e,
+ struct blame_origin *parent,
+ struct blame_origin *target,
+ struct blame_entry **diffp,
+ struct blame_entry **ignoredp,
+ struct blame_line_tracker *line_blames)
+{
+ int entry_len, nr_lines, i;
+
+ /*
+ * We carve new entries off the front of e. Each entry comes from a
+ * contiguous chunk of lines: adjacent lines from the same origin
+ * (either the parent or the target).
+ */
+ entry_len = 1;
+ nr_lines = e->num_lines; /* e changes in the loop */
+ for (i = 0; i < nr_lines; i++) {
+ struct blame_entry *next = NULL;
+
+ /*
+ * We are often adjacent to the next line - only split the blame
+ * entry when we have to.
+ */
+ if (i + 1 < nr_lines) {
+ if (are_lines_adjacent(&line_blames[i],
+ &line_blames[i + 1])) {
+ entry_len++;
+ continue;
+ }
+ next = split_blame_at(e, entry_len,
+ blame_origin_incref(e->suspect));
+ }
+ if (line_blames[i].is_parent) {
+ blame_origin_decref(e->suspect);
+ e->suspect = blame_origin_incref(parent);
+ e->s_lno = line_blames[i - entry_len + 1].s_lno;
+ e->next = *ignoredp;
+ *ignoredp = e;
+ } else {
+ /* e->s_lno is already in the target's address space. */
+ e->next = *diffp;
+ *diffp = e;
+ }
+ assert(e->num_lines == entry_len);
+ e = next;
+ entry_len = 1;
+ }
+ assert(!e);
+}
+
/*
* Process one hunk from the patch between the current suspect for
* blame_entry e and its parent. This first blames any unfinished
@@ -868,13 +965,20 @@ static struct blame_entry *split_blame_at(struct blame_entry *e, int len,
* -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.
+ *
+ * tlno: line number in the target where this chunk begins
+ * same: line number in the target where this chunk ends
+ * offset: add to tlno to get the chunk starting point in the parent
+ * parent_len: number of lines in the parent chunk
*/
static void blame_chunk(struct blame_entry ***dstq, struct blame_entry ***srcq,
- int tlno, int offset, int same,
- struct blame_origin *parent)
+ int tlno, int offset, int same, int parent_len,
+ struct blame_origin *parent,
+ struct blame_origin *target, int ignore_diffs)
{
struct blame_entry *e = **srcq;
- struct blame_entry *samep = NULL, *diffp = NULL;
+ struct blame_entry *samep = NULL, *diffp = NULL, *ignoredp = NULL;
+ struct blame_line_tracker *line_blames = NULL;
while (e && e->s_lno < tlno) {
struct blame_entry *next = e->next;
@@ -923,6 +1027,14 @@ static void blame_chunk(struct blame_entry ***dstq, struct blame_entry ***srcq,
*/
samep = NULL;
diffp = NULL;
+
+ if (ignore_diffs && same - tlno > 0) {
+ line_blames = xcalloc(sizeof(struct blame_line_tracker),
+ same - tlno);
+ guess_line_blames(parent, target, tlno, offset, same,
+ parent_len, line_blames);
+ }
+
while (e && e->s_lno < same) {
struct blame_entry *next = e->next;
@@ -942,10 +1054,29 @@ static void blame_chunk(struct blame_entry ***dstq, struct blame_entry ***srcq,
n->next = samep;
samep = n;
}
- e->next = diffp;
- diffp = e;
+ if (ignore_diffs) {
+ ignore_blame_entry(e, parent, target, &diffp, &ignoredp,
+ line_blames + e->s_lno - tlno);
+ } else {
+ e->next = diffp;
+ diffp = e;
+ }
e = next;
}
+ free(line_blames);
+ if (ignoredp) {
+ /*
+ * Note ignoredp is not sorted yet, and thus neither is dstq.
+ * That list must be sorted before we queue_blames(). We defer
+ * sorting until after all diff hunks are processed, so that
+ * guess_line_blames() can pick *any* line in the parent. The
+ * slight drawback is that we end up sorting all blame entries
+ * passed to the parent, including those that are unrelated to
+ * changes made by the ignored commit.
+ */
+ **dstq = reverse_blame(ignoredp, **dstq);
+ *dstq = &ignoredp->next;
+ }
**srcq = reverse_blame(diffp, reverse_blame(samep, e));
/* Move across elements that are in the unblamable portion */
if (diffp)
@@ -954,7 +1085,9 @@ static void blame_chunk(struct blame_entry ***dstq, struct blame_entry ***srcq,
struct blame_chunk_cb_data {
struct blame_origin *parent;
+ struct blame_origin *target;
long offset;
+ int ignore_diffs;
struct blame_entry **dstq;
struct blame_entry **srcq;
};
@@ -967,7 +1100,8 @@ static int blame_chunk_cb(long start_a, long count_a,
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);
+ start_b + count_b, count_a, d->parent, d->target,
+ d->ignore_diffs);
d->offset = start_a + count_a - (start_b + count_b);
return 0;
}
@@ -979,7 +1113,7 @@ static int blame_chunk_cb(long start_a, long count_a,
*/
static void pass_blame_to_parent(struct blame_scoreboard *sb,
struct blame_origin *target,
- struct blame_origin *parent)
+ struct blame_origin *parent, int ignore_diffs)
{
mmfile_t file_p, file_o;
struct blame_chunk_cb_data d;
@@ -989,7 +1123,9 @@ static void pass_blame_to_parent(struct blame_scoreboard *sb,
return; /* nothing remains for this target */
d.parent = parent;
+ d.target = target;
d.offset = 0;
+ d.ignore_diffs = ignore_diffs;
d.dstq = &newdest; d.srcq = &target->suspects;
fill_origin_blob(&sb->revs->diffopt, parent, &file_p, &sb->num_read_blob);
@@ -1001,8 +1137,13 @@ static void pass_blame_to_parent(struct blame_scoreboard *sb,
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);
+ blame_chunk(&d.dstq, &d.srcq, INT_MAX, d.offset, INT_MAX, 0,
+ parent, target, 0);
*d.dstq = NULL;
+ if (ignore_diffs)
+ newdest = llist_mergesort(newdest, get_next_blame,
+ set_next_blame,
+ compare_blame_suspect);
queue_blames(sb, parent, newdest);
return;
@@ -1506,12 +1647,29 @@ static void pass_blame(struct blame_scoreboard *sb, struct blame_origin *origin,
blame_origin_incref(porigin);
origin->previous = porigin;
}
- pass_blame_to_parent(sb, origin, porigin);
+ pass_blame_to_parent(sb, origin, porigin, 0);
if (!origin->suspects)
goto finish;
}
/*
+ * Pass remaining suspects for ignored commits to their parents.
+ */
+ if (oidset_contains(&sb->ignore_list, &commit->object.oid)) {
+ for (i = 0, sg = first_scapegoat(revs, commit, sb->reverse);
+ i < num_sg && sg;
+ sg = sg->next, i++) {
+ struct blame_origin *porigin = sg_origin[i];
+
+ if (!porigin)
+ continue;
+ pass_blame_to_parent(sb, origin, porigin, 1);
+ if (!origin->suspects)
+ goto finish;
+ }
+ }
+
+ /*
* Optionally find moves in parents' files.
*/
if (opt & PICKAXE_BLAME_MOVE) {