git-blame accepts only zero or one -L option. Clients requiring blame information for multiple disjoint ranges are therefore forced either to invoke git-blame multiple times, once for each range, or only once with no -L option to cover the entire file, which can be costly. Teach git-blame to accept multiple -L ranges. Overlapping and out-of-order ranges are accepted and handled gracefully. For example: git blame -L 3,+4 -L 91,+7 -L 2,3 -L 89,100 source.c emits blame information for lines 2-6 and 89-100. Signed-off-by: Eric Sunshine <sunshine@xxxxxxxxxxxxxx> --- This is RFC because it lacks documentation and test updates, and because I want to make sure the approach is sound and not abusive of the blame machinery. Rather than sorting and coalescing input -L ranges manually, existing add_blame_range() and coalesce() are (ab)used to normalize the input. This requires a small change to coalesce() to deal with potentially overlapping ranges since it never otherwise encounters overlap during normal blame operation. This patch is somewhat less scary when whitespace changes are ignored. builtin/blame.c | 70 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/builtin/blame.c b/builtin/blame.c index 079dcd3..f26ff44 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -278,8 +278,11 @@ static void coalesce(struct scoreboard *sb) for (ent = sb->ent; ent && (next = ent->next); ent = next) { if (same_suspect(ent->suspect, next->suspect) && ent->guilty == next->guilty && - ent->s_lno + ent->num_lines == next->s_lno) { - ent->num_lines += next->num_lines; + ent->s_lno + ent->num_lines >= next->s_lno) { + int ent_top = ent->lno + ent->num_lines; + int next_top = next->lno + next->num_lines; + if (ent_top < next_top) + ent->num_lines = next_top - ent->s_lno; ent->next = next->next; if (ent->next) ent->next->prev = ent; @@ -2245,17 +2248,6 @@ static int blame_move_callback(const struct option *option, const char *arg, int return 0; } -static int blame_bottomtop_callback(const struct option *option, const char *arg, int unset) -{ - const char **bottomtop = option->value; - if (!arg) - return -1; - if (*bottomtop) - die("More than one '-L n,m' option given"); - *bottomtop = arg; - return 0; -} - int cmd_blame(int argc, const char **argv, const char *prefix) { struct rev_info revs; @@ -2263,11 +2255,11 @@ int cmd_blame(int argc, const char **argv, const char *prefix) struct scoreboard sb; struct origin *o; struct blame_entry *ent; - long dashdash_pos, bottom, top, lno; + long dashdash_pos, lno; const char *final_commit_name = NULL; enum object_type type; - static const char *bottomtop = NULL; + static struct string_list ranges; static int output_option = 0, opt = 0; static int show_stats = 0; static const char *revs_file = NULL; @@ -2293,13 +2285,14 @@ int cmd_blame(int argc, const char **argv, const char *prefix) OPT_STRING(0, "contents", &contents_from, N_("file"), N_("Use <file>'s contents as the final image")), { OPTION_CALLBACK, 'C', NULL, &opt, N_("score"), N_("Find line copies within and across files"), PARSE_OPT_OPTARG, blame_copy_callback }, { OPTION_CALLBACK, 'M', NULL, &opt, N_("score"), N_("Find line movements within and across files"), PARSE_OPT_OPTARG, blame_move_callback }, - OPT_CALLBACK('L', NULL, &bottomtop, N_("n,m"), N_("Process only line range n,m, counting from 1"), blame_bottomtop_callback), + OPT_STRING_LIST('L', NULL, &ranges, N_("n,m"), N_("Process only line range n,m, counting from 1")), OPT__ABBREV(&abbrev), OPT_END() }; struct parse_opt_ctx_t ctx; int cmd_is_annotate = !strcmp(argv[0], "annotate"); + unsigned int range_i; git_config(git_blame_config, NULL); init_revisions(&revs, NULL); @@ -2492,24 +2485,33 @@ parse_done: num_read_blob++; lno = prepare_lines(&sb); - bottom = top = 0; - if (bottomtop) - prepare_blame_range(&sb, bottomtop, lno, &bottom, &top); - if (bottom < 1) - bottom = 1; - if (top < 1) - top = lno; - bottom--; - if (lno < top || lno < bottom) - die("file %s has only %lu lines", path, lno); - - ent = xcalloc(1, sizeof(*ent)); - ent->lno = bottom; - ent->num_lines = top - bottom; - ent->suspect = o; - ent->s_lno = bottom; - - sb.ent = ent; + if (!ranges.nr) + string_list_append(&ranges, xstrdup("0,0")); + + for (range_i = 0; range_i < ranges.nr; ++range_i) { + long bottom, top; + prepare_blame_range(&sb, ranges.items[range_i].string, + lno, &bottom, &top); + if (bottom < 1) + bottom = 1; + if (top < 1) + top = lno; + bottom--; + if (lno < top || lno < bottom) + die("file %s has only %lu lines", path, lno); + + ent = xcalloc(1, sizeof(*ent)); + ent->lno = bottom; + ent->num_lines = top - bottom; + ent->suspect = o; + ent->s_lno = bottom; + add_blame_entry(&sb, ent); + } + coalesce(&sb); + + origin_decref(o); + string_list_clear(&ranges, 0); + sb.path = path; read_mailmap(&mailmap, NULL); -- 1.8.3.2 -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html