git/list[1] front-page[2] threads[3] people[4] search[5] about
 

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

From
Eric Sunshine <sunshine@sunshineco.com>
Date
Jul 7, 2013, 08:45 UTC
Message-ID
<1373186706-19284-1-git-send-email-sunshine@sunshineco.com>

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@sunshineco.com>
---

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
Next: Junio C Hamano
Message 1 of 22 in “blame: accept multiple -L ranges”
  1. blame: accept multiple -L rangesEric Sunshine, Jul 7, 2013
  2. Junio C HamanoJul 7, 2013
  3. Eric SunshineJul 9, 2013
  4. Junio C HamanoJul 9, 2013
  5. Eric SunshineJul 9, 2013
  6. Thomas RastJul 9, 2013
  7. Eric SunshineJul 9, 2013
  8. Junio C HamanoJul 9, 2013
  9. Eric SunshineJul 9, 2013
  10. Thomas RastJul 9, 2013
  11. Jonathan NiederJul 9, 2013
  12. Junio C HamanoJul 9, 2013
  13. Michael HaggertyJul 9, 2013
  14. Junio C HamanoJul 9, 2013
  15. Eric SunshineJul 22, 2013
  16. Thomas RastJul 22, 2013
  17. Junio C HamanoJul 22, 2013
  18. Thomas RastJul 22, 2013
  19. Junio C HamanoJul 22, 2013
  20. Thomas RastJul 10, 2013
  21. Junio C HamanoJul 11, 2013
  22. Junio C HamanoJul 9, 2013

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.