[PATCH/RFC] blame: accept multiple -L ranges

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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



[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]