blame: wrap blame_sort and compare_blame_final
[gitweb.git] / builtin / blame.c
index 129ef288780c09b63d4055a0f99415a97dbe6a0f..61fd5b477acb729856ebfd1a6395af8d4e4955fa 100644 (file)
@@ -61,20 +61,11 @@ 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
@@ -134,7 +125,7 @@ struct progress_info {
 };
 
 static int diff_hunks(mmfile_t *file_a, mmfile_t *file_b,
-                     xdl_emit_hunk_consume_func_t hunk_func, void *cb_data)
+                     xdl_emit_hunk_consume_func_t hunk_func, void *cb_data, int xdl_opts)
 {
        xpparam_t xpp = {0};
        xdemitconf_t xecfg = {0};
@@ -151,13 +142,13 @@ static int diff_hunks(mmfile_t *file_a, mmfile_t *file_b,
  * diff machinery
  */
 static void fill_origin_blob(struct diff_options *opt,
-                            struct blame_origin *o, mmfile_t *file)
+                            struct blame_origin *o, mmfile_t *file, int *num_read_blob)
 {
        if (!o->file.ptr) {
                enum object_type type;
                unsigned long file_size;
 
-               num_read_blob++;
+               (*num_read_blob)++;
                if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) &&
                    textconv_object(o->path, o->mode, &o->blob_oid, 1, &file->ptr, &file_size))
                        ;
@@ -337,12 +328,6 @@ static int compare_blame_suspect(const void *p1, const void *p2)
        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)
@@ -375,8 +360,42 @@ struct blame_scoreboard {
        /* look-up a line in the final buffer */
        int num_lines;
        int *lineno;
+
+       /* stats */
+       int num_read_blob;
+       int num_get_patch;
+       int num_commits;
+
+       /*
+        * blame for a blame_entry with score lower than these thresholds
+        * is not passed to the parent using move/copy logic.
+        */
+       unsigned move_score;
+       unsigned copy_score;
+
+       /* use this file's contents as the final image */
+       const char *contents_from;
+
+       /* flags */
+       int reverse;
+       int show_root;
+       int xdl_opts;
+       int no_whole_file_rename;
+       int debug;
+
+       /* callbacks */
+       void(*on_sanity_fail)(struct blame_scoreboard *, int);
+       void(*found_guilty_entry)(struct blame_entry *, void *);
+
+       void *found_guilty_entry_data;
 };
 
+static void blame_sort_final(struct blame_scoreboard *sb)
+{
+       sb->ent = llist_mergesort(sb->ent, get_next_blame, set_next_blame,
+                                 compare_blame_final);
+}
+
 static void sanity_check_refcnt(struct blame_scoreboard *);
 
 /*
@@ -400,7 +419,7 @@ static void blame_coalesce(struct blame_scoreboard *sb)
                }
        }
 
-       if (DEBUG) /* sanity */
+       if (sb->debug) /* sanity */
                sanity_check_refcnt(sb);
 }
 
@@ -653,14 +672,14 @@ static void dup_entry(struct blame_entry ***queue,
        *queue = &dst->next;
 }
 
-static const char *nth_line(struct blame_scoreboard *sb, long lno)
+static const char *blame_nth_line(struct blame_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 blame_scoreboard *)data, lno);
+       return blame_nth_line((struct blame_scoreboard *)data, lno);
 }
 
 /*
@@ -934,11 +953,11 @@ static void pass_blame_to_parent(struct blame_scoreboard *sb,
        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++;
+       fill_origin_blob(&sb->revs->diffopt, parent, &file_p, &sb->num_read_blob);
+       fill_origin_blob(&sb->revs->diffopt, target, &file_o, &sb->num_read_blob);
+       sb->num_get_patch++;
 
-       if (diff_hunks(&file_p, &file_o, blame_chunk_cb, &d))
+       if (diff_hunks(&file_p, &file_o, blame_chunk_cb, &d, sb->xdl_opts))
                die("unable to generate diff (%s -> %s)",
                    oid_to_hex(&parent->commit->object.oid),
                    oid_to_hex(&target->commit->object.oid));
@@ -968,8 +987,8 @@ static unsigned blame_entry_score(struct blame_scoreboard *sb, struct blame_entr
                return e->score;
 
        score = 1;
-       cp = nth_line(sb, e->lno);
-       ep = nth_line(sb, e->lno + e->num_lines);
+       cp = blame_nth_line(sb, e->lno);
+       ep = blame_nth_line(sb, e->lno + e->num_lines);
        while (cp < ep) {
                unsigned ch = *((unsigned char *)cp);
                if (isalnum(ch))
@@ -1078,16 +1097,16 @@ static void find_copy_in_blob(struct blame_scoreboard *sb,
        /*
         * Prepare mmfile that contains only the lines in ent.
         */
-       cp = nth_line(sb, ent->lno);
+       cp = blame_nth_line(sb, ent->lno);
        file_o.ptr = (char *) cp;
-       file_o.size = nth_line(sb, ent->lno + ent->num_lines) - cp;
+       file_o.size = blame_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))
+       if (diff_hunks(file_p, &file_o, handle_split_cb, &d, sb->xdl_opts))
                die("unable to generate diff (%s)",
                    oid_to_hex(&parent->commit->object.oid));
        /* remainder, if any, all match the preimage */
@@ -1140,7 +1159,7 @@ static void find_move_in_parent(struct blame_scoreboard *sb,
        if (!unblamed)
                return; /* nothing remains for this target */
 
-       fill_origin_blob(&sb->revs->diffopt, parent, &file_p);
+       fill_origin_blob(&sb->revs->diffopt, parent, &file_p, &sb->num_read_blob);
        if (!file_p.ptr)
                return;
 
@@ -1156,7 +1175,7 @@ static void find_move_in_parent(struct blame_scoreboard *sb,
                        next = e->next;
                        find_copy_in_blob(sb, e, parent, split, &file_p);
                        if (split[1].suspect &&
-                           blame_move_score < blame_entry_score(sb, &split[1])) {
+                           sb->move_score < blame_entry_score(sb, &split[1])) {
                                split_blame(blamed, &unblamedtail, split, e);
                        } else {
                                e->next = leftover;
@@ -1165,7 +1184,7 @@ static void find_move_in_parent(struct blame_scoreboard *sb,
                        decref_split(split);
                }
                *unblamedtail = NULL;
-               toosmall = filter_small(sb, toosmall, &unblamed, blame_move_score);
+               toosmall = filter_small(sb, toosmall, &unblamed, sb->move_score);
        } while (unblamed);
        target->suspects = reverse_blame(leftover, NULL);
 }
@@ -1269,7 +1288,7 @@ static void find_copy_in_parent(struct blame_scoreboard *sb,
                        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);
+                       fill_origin_blob(&sb->revs->diffopt, norigin, &file_p, &sb->num_read_blob);
                        if (!file_p.ptr)
                                continue;
 
@@ -1286,7 +1305,7 @@ static void find_copy_in_parent(struct blame_scoreboard *sb,
                for (j = 0; j < num_ents; j++) {
                        struct blame_entry *split = blame_list[j].split;
                        if (split[1].suspect &&
-                           blame_copy_score < blame_entry_score(sb, &split[1])) {
+                           sb->copy_score < blame_entry_score(sb, &split[1])) {
                                split_blame(blamed, &unblamedtail, split,
                                            blame_list[j].ent);
                        } else {
@@ -1297,7 +1316,7 @@ static void find_copy_in_parent(struct blame_scoreboard *sb,
                }
                free(blame_list);
                *unblamedtail = NULL;
-               toosmall = filter_small(sb, toosmall, &unblamed, blame_copy_score);
+               toosmall = filter_small(sb, toosmall, &unblamed, sb->copy_score);
        } while (unblamed);
        target->suspects = reverse_blame(leftover, NULL);
        diff_flush(&diff_opts);
@@ -1333,7 +1352,8 @@ static void pass_whole_blame(struct blame_scoreboard *sb,
  * "parent" (and "porigin"), but what we mean is to find scapegoat to
  * exonerate ourselves.
  */
-static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit *commit)
+static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit *commit,
+                                          int reverse)
 {
        if (!reverse) {
                if (revs->first_parent_only &&
@@ -1347,9 +1367,9 @@ static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit
        return lookup_decoration(&revs->children, &commit->object);
 }
 
-static int num_scapegoats(struct rev_info *revs, struct commit *commit)
+static int num_scapegoats(struct rev_info *revs, struct commit *commit, int reverse)
 {
-       struct commit_list *l = first_scapegoat(revs, commit);
+       struct commit_list *l = first_scapegoat(revs, commit, reverse);
        return commit_list_count(l);
 }
 
@@ -1358,7 +1378,8 @@ static int num_scapegoats(struct rev_info *revs, struct commit *commit)
  */
 static void distribute_blame(struct blame_scoreboard *sb, struct blame_entry *blamed)
 {
-       blamed = blame_sort(blamed, compare_blame_suspect);
+       blamed = llist_mergesort(blamed, get_next_blame, set_next_blame,
+                                compare_blame_suspect);
        while (blamed)
        {
                struct blame_origin *porigin = blamed->suspect;
@@ -1387,7 +1408,7 @@ static void pass_blame(struct blame_scoreboard *sb, struct blame_origin *origin,
        struct blame_entry *toosmall = NULL;
        struct blame_entry *blames, **blametail = &blames;
 
-       num_sg = num_scapegoats(revs, commit);
+       num_sg = num_scapegoats(revs, commit, sb->reverse);
        if (!num_sg)
                goto finish;
        else if (num_sg < ARRAY_SIZE(sg_buf))
@@ -1399,11 +1420,11 @@ static void pass_blame(struct blame_scoreboard *sb, struct blame_origin *origin,
         * The first pass looks for unrenamed path to optimize for
         * common cases, then we look for renames in the second pass.
         */
-       for (pass = 0; pass < 2 - no_whole_file_rename; pass++) {
+       for (pass = 0; pass < 2 - sb->no_whole_file_rename; pass++) {
                struct blame_origin *(*find)(struct commit *, struct blame_origin *);
                find = pass ? find_rename : find_origin;
 
-               for (i = 0, sg = first_scapegoat(revs, commit);
+               for (i = 0, sg = first_scapegoat(revs, commit, sb->reverse);
                     i < num_sg && sg;
                     sg = sg->next, i++) {
                        struct commit *p = sg->item;
@@ -1434,8 +1455,8 @@ static void pass_blame(struct blame_scoreboard *sb, struct blame_origin *origin,
                }
        }
 
-       num_commits++;
-       for (i = 0, sg = first_scapegoat(revs, commit);
+       sb->num_commits++;
+       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];
@@ -1454,9 +1475,9 @@ static void pass_blame(struct blame_scoreboard *sb, struct blame_origin *origin,
         * Optionally find moves in parents' files.
         */
        if (opt & PICKAXE_BLAME_MOVE) {
-               filter_small(sb, &toosmall, &origin->suspects, blame_move_score);
+               filter_small(sb, &toosmall, &origin->suspects, sb->move_score);
                if (origin->suspects) {
-                       for (i = 0, sg = first_scapegoat(revs, commit);
+                       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];
@@ -1473,17 +1494,17 @@ static void pass_blame(struct blame_scoreboard *sb, struct blame_origin *origin,
         * Optionally find copies from parents' files.
         */
        if (opt & PICKAXE_BLAME_COPY) {
-               if (blame_copy_score > blame_move_score)
-                       filter_small(sb, &toosmall, &origin->suspects, blame_copy_score);
-               else if (blame_copy_score < blame_move_score) {
+               if (sb->copy_score > sb->move_score)
+                       filter_small(sb, &toosmall, &origin->suspects, sb->copy_score);
+               else if (sb->copy_score < sb->move_score) {
                        origin->suspects = blame_merge(origin->suspects, toosmall);
                        toosmall = NULL;
-                       filter_small(sb, &toosmall, &origin->suspects, blame_copy_score);
+                       filter_small(sb, &toosmall, &origin->suspects, sb->copy_score);
                }
                if (!origin->suspects)
                        goto finish;
 
-               for (i = 0, sg = first_scapegoat(revs, commit);
+               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];
@@ -1712,9 +1733,10 @@ static int emit_one_suspect_detail(struct blame_origin *suspect, int repeat)
  * The blame_entry is found to be guilty for the range.
  * Show it in incremental output.
  */
-static void found_guilty_entry(struct blame_entry *ent,
-                          struct progress_info *pi)
+static void found_guilty_entry(struct blame_entry *ent, void *data)
 {
+       struct progress_info *pi = (struct progress_info *)data;
+
        if (incremental) {
                struct blame_origin *suspect = ent->suspect;
 
@@ -1737,11 +1759,6 @@ static void assign_blame(struct blame_scoreboard *sb, int opt)
 {
        struct rev_info *revs = sb->revs;
        struct commit *commit = prio_queue_get(&sb->commits);
-       struct progress_info pi = { NULL, 0 };
-
-       if (show_progress)
-               pi.progress = start_progress_delay(_("Blaming lines"),
-                                                  sb->num_lines, 50, 1);
 
        while (commit) {
                struct blame_entry *ent;
@@ -1764,7 +1781,7 @@ static void assign_blame(struct blame_scoreboard *sb, int opt)
                 */
                blame_origin_incref(suspect);
                parse_commit(commit);
-               if (reverse ||
+               if (sb->reverse ||
                    (!(commit->object.flags & UNINTERESTING) &&
                     !(revs->max_age != -1 && commit->date < revs->max_age)))
                        pass_blame(sb, suspect, opt);
@@ -1774,7 +1791,7 @@ static void assign_blame(struct blame_scoreboard *sb, int opt)
                                mark_parents_uninteresting(commit);
                }
                /* treat root commit as boundary */
-               if (!commit->parents && !show_root)
+               if (!commit->parents && !sb->show_root)
                        commit->object.flags |= UNINTERESTING;
 
                /* Take responsibility for the remaining entries */
@@ -1783,7 +1800,8 @@ static void assign_blame(struct blame_scoreboard *sb, int opt)
                        suspect->guilty = 1;
                        for (;;) {
                                struct blame_entry *next = ent->next;
-                               found_guilty_entry(ent, &pi);
+                               if (sb->found_guilty_entry)
+                                       sb->found_guilty_entry(ent, sb->found_guilty_entry_data);
                                if (next) {
                                        ent = next;
                                        continue;
@@ -1796,11 +1814,9 @@ static void assign_blame(struct blame_scoreboard *sb, int opt)
                }
                blame_origin_decref(suspect);
 
-               if (DEBUG) /* sanity */
+               if (sb->debug) /* sanity */
                        sanity_check_refcnt(sb);
        }
-
-       stop_progress(&pi.progress);
 }
 
 static const char *format_time(timestamp_t time, const char *tz_str,
@@ -1866,7 +1882,7 @@ static void emit_porcelain(struct blame_scoreboard *sb, struct blame_entry *ent,
               ent->num_lines);
        emit_porcelain_details(suspect, repeat);
 
-       cp = nth_line(sb, ent->lno);
+       cp = blame_nth_line(sb, ent->lno);
        for (cnt = 0; cnt < ent->num_lines; cnt++) {
                char ch;
                if (cnt) {
@@ -1900,7 +1916,7 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int
        get_commit_info(suspect->commit, &ci, 1);
        oid_to_hex_r(hex, &suspect->commit->object.oid);
 
-       cp = nth_line(sb, ent->lno);
+       cp = blame_nth_line(sb, ent->lno);
        for (cnt = 0; cnt < ent->num_lines; cnt++) {
                char ch;
                int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? GIT_SHA1_HEXSZ : abbrev;
@@ -2135,12 +2151,16 @@ static void sanity_check_refcnt(struct blame_scoreboard *sb)
                        baa = 1;
                }
        }
-       if (baa) {
-               int opt = 0160;
-               find_alignment(sb, &opt);
-               output(sb, opt);
-               die("Baa %d!", baa);
-       }
+       if (baa)
+               sb->on_sanity_fail(sb, baa);
+}
+
+static void sanity_check_on_fail(struct blame_scoreboard *sb, int baa)
+{
+       int opt = OUTPUT_SHOW_SCORE | OUTPUT_SHOW_NUMBER | OUTPUT_SHOW_NAME;
+       find_alignment(sb, &opt);
+       output(sb, opt);
+       die("Baa %d!", baa);
 }
 
 static unsigned parse_score(const char *arg)
@@ -2529,6 +2549,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        char *final_commit_name = NULL;
        enum object_type type;
        struct commit *final_commit = NULL;
+       struct progress_info pi = { NULL, 0 };
 
        struct string_list range_list = STRING_LIST_INIT_NODUP;
        int output_option = 0, opt = 0;
@@ -2675,11 +2696,6 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                opt |= (PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE |
                        PICKAXE_BLAME_COPY_HARDER);
 
-       if (!blame_move_score)
-               blame_move_score = BLAME_DEFAULT_MOVE_SCORE;
-       if (!blame_copy_score)
-               blame_copy_score = BLAME_DEFAULT_COPY_SCORE;
-
        /*
         * We have collected options unknown to us in argv[1..unk]
         * which are to be passed to revision machinery if we are
@@ -2733,8 +2749,12 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        revs.disable_stdin = 1;
        setup_revisions(argc, argv, &revs, NULL);
        memset(&sb, 0, sizeof(sb));
+       sb.move_score = BLAME_DEFAULT_MOVE_SCORE;
+       sb.copy_score = BLAME_DEFAULT_COPY_SCORE;
 
        sb.revs = &revs;
+       sb.contents_from = contents_from;
+       sb.reverse = reverse;
        if (!reverse) {
                final_commit_name = prepare_final(&sb);
                sb.commits.compare = compare_commits_by_commit_date;
@@ -2818,7 +2838,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                            oid_to_hex(&o->blob_oid),
                            path);
        }
-       num_read_blob++;
+       sb.num_read_blob++;
        lno = prepare_lines(&sb);
 
        if (lno && !range_list.nr)
@@ -2871,10 +2891,30 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        sb.ent = NULL;
        sb.path = path;
 
+       if (blame_move_score)
+               sb.move_score = blame_move_score;
+       if (blame_copy_score)
+               sb.copy_score = blame_copy_score;
+
+       sb.debug = DEBUG;
+       sb.on_sanity_fail = &sanity_check_on_fail;
+
+       sb.show_root = show_root;
+       sb.xdl_opts = xdl_opts;
+       sb.no_whole_file_rename = no_whole_file_rename;
+
        read_mailmap(&mailmap, NULL);
 
+       sb.found_guilty_entry = &found_guilty_entry;
+       sb.found_guilty_entry_data = &pi;
+       if (show_progress)
+               pi.progress = start_progress_delay(_("Blaming lines"),
+                                                  sb.num_lines, 50, 1);
+
        assign_blame(&sb, opt);
 
+       stop_progress(&pi.progress);
+
        if (!incremental)
                setup_pager();
 
@@ -2883,7 +2923,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        if (incremental)
                return 0;
 
-       sb.ent = blame_sort(sb.ent, compare_blame_final);
+       blame_sort_final(&sb);
 
        blame_coalesce(&sb);
 
@@ -2899,9 +2939,9 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        }
 
        if (show_stats) {
-               printf("num read blob: %d\n", num_read_blob);
-               printf("num get patch: %d\n", num_get_patch);
-               printf("num commits: %d\n", num_commits);
+               printf("num read blob: %d\n", sb.num_read_blob);
+               printf("num get patch: %d\n", sb.num_get_patch);
+               printf("num commits: %d\n", sb.num_commits);
        }
        return 0;
 }