Git development
 help / color / mirror / Atom feed
From: "René Scharfe" <l.s.r@web.de>
To: Laszlo Ersek <laszlo.ersek@posteo.net>, git@vger.kernel.org
Subject: [PATCH] blame: reserve mark column only if necessary
Date: Mon, 6 Jul 2026 10:38:25 +0200	[thread overview]
Message-ID: <92991b5e-0667-4315-89d5-1514a5499297@web.de> (raw)
In-Reply-To: <b500479b-14c1-4fbb-a672-1d2cd4852601@posteo.net>

git blame prepends commit hashes of boundary commits with "^", ignored
commits with "?" and unblamable commits with "*" and reserves one column
for them by extending the hash abbreviation, to avoid showing ambiguous
hashes.

This reserved column wastes precious screen space, which can be
especially irritating when using the option -b to blank out boundary
commit hashes and not ignoring any commits.  Reserve it only as needed,
i.e. if any of those cases are actually shown.

Pointed-out-by: Laszlo Ersek <laszlo.ersek@posteo.net>
Signed-off-by: René Scharfe <l.s.r@web.de>
---
 Documentation/git-blame.adoc | 11 +++---
 builtin/blame.c              | 68 ++++++++++++++++++++++++------------
 t/t8002-blame.sh             |  7 ++--
 3 files changed, 53 insertions(+), 33 deletions(-)

diff --git a/Documentation/git-blame.adoc b/Documentation/git-blame.adoc
index 8808009e87e..2b74e455997 100644
--- a/Documentation/git-blame.adoc
+++ b/Documentation/git-blame.adoc
@@ -88,11 +88,12 @@ include::blame-options.adoc[]
 include::diff-algorithm-option.adoc[]
 
 `--abbrev=<n>`::
-	Instead of using the default _7+1_ hexadecimal digits as the
-	abbreviated object name, use _<m>+1_ digits, where _<m>_ is at
-	least _<n>_ but ensures the commit object names are unique.
-	Note that 1 column
-	is used for a caret to mark the boundary commit.
+	Instead of using the default _7_ hexadecimal digits as the
+	abbreviated object name, use at least _<n>_ digits, but ensure
+	the commit object names are unique.
+	If commits marked with caret (boundary), question mark (ignored)
+	or asterisk (unblamable) are shown, extend unmarked object names
+	to align them.
 
 
 THE DEFAULT FORMAT
diff --git a/builtin/blame.c b/builtin/blame.c
index ffbd3ce5c5a..5ae39d0458a 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -453,6 +453,36 @@ static void determine_line_heat(struct commit_info *ci, const char **dest_color)
 	*dest_color = colorfield[i].col;
 }
 
+static inline int maybe_putc(int c, FILE *out)
+{
+	return out ? putc(c, out) : 0;
+}
+
+static size_t print_marks(FILE *out, const struct blame_entry *ent, int opt)
+{
+	size_t len = 0;
+
+	if ((ent->suspect->commit->object.flags & UNINTERESTING) &&
+	    !blank_boundary && !(opt & OUTPUT_ANNOTATE_COMPAT)) {
+		maybe_putc('^', out);
+		len++;
+	}
+	if (mark_unblamable_lines && ent->unblamable) {
+		maybe_putc('*', out);
+		len++;
+	}
+	if (mark_ignored_lines && ent->ignored) {
+		maybe_putc('?', out);
+		len++;
+	}
+	return len;
+}
+
+static size_t count_marks(const struct blame_entry *ent, int opt)
+{
+	return print_marks(NULL, ent, opt);
+}
+
 static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent,
 		       int opt, struct blame_entry *prev_ent)
 {
@@ -499,23 +529,10 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent,
 		if (color)
 			fputs(color, stdout);
 
-		if (suspect->commit->object.flags & UNINTERESTING) {
-			if (blank_boundary) {
-				memset(hex, ' ', strlen(hex));
-			} else if (!(opt & OUTPUT_ANNOTATE_COMPAT)) {
-				length--;
-				putchar('^');
-			}
-		}
-
-		if (mark_unblamable_lines && ent->unblamable) {
-			length--;
-			putchar('*');
-		}
-		if (mark_ignored_lines && ent->ignored) {
-			length--;
-			putchar('?');
-		}
+		if ((suspect->commit->object.flags & UNINTERESTING) &&
+		    blank_boundary)
+			memset(hex, ' ', strlen(hex));
+		length -= print_marks(stdout, ent, opt);
 
 		printf("%.*s", (int)(length < GIT_MAX_HEXSZ ? length : GIT_MAX_HEXSZ), hex);
 		if (opt & OUTPUT_ANNOTATE_COMPAT) {
@@ -647,11 +664,15 @@ static void find_alignment(struct blame_scoreboard *sb, int *option)
 	struct blame_entry *e;
 	int compute_auto_abbrev = (abbrev < 0);
 	int auto_abbrev = DEFAULT_ABBREV;
+	size_t max_marks_count = 0;
 
 	for (e = sb->ent; e; e = e->next) {
 		struct blame_origin *suspect = e->suspect;
 		int num;
+		size_t marks_count = count_marks(e, *option);
 
+		if (max_marks_count < marks_count)
+			max_marks_count = marks_count;
 		if (compute_auto_abbrev)
 			auto_abbrev = update_auto_abbrev(auto_abbrev, suspect);
 		if (strcmp(suspect->path, sb->path))
@@ -685,8 +706,12 @@ static void find_alignment(struct blame_scoreboard *sb, int *option)
 	max_score_digits = decimal_width(largest_score);
 
 	if (compute_auto_abbrev)
-		/* one more abbrev length is needed for the boundary commit */
-		abbrev = auto_abbrev + 1;
+		abbrev = auto_abbrev;
+	if (abbrev < (int)the_hash_algo->hexsz) {
+		abbrev += max_marks_count;
+		if (abbrev > (int)the_hash_algo->hexsz)
+			abbrev = the_hash_algo->hexsz;
+	}
 }
 
 static void sanity_check_on_fail(struct blame_scoreboard *sb, int baa)
@@ -1047,10 +1072,7 @@ int cmd_blame(int argc,
 	} else if (show_progress < 0)
 		show_progress = isatty(2);
 
-	if (0 < abbrev && abbrev < (int)the_hash_algo->hexsz)
-		/* one more abbrev length is needed for the boundary commit */
-		abbrev++;
-	else if (!abbrev)
+	if (!abbrev)
 		abbrev = the_hash_algo->hexsz;
 
 	if (revs_file && read_ancestry(revs_file))
diff --git a/t/t8002-blame.sh b/t/t8002-blame.sh
index 7822947f028..bf04b8273ef 100755
--- a/t/t8002-blame.sh
+++ b/t/t8002-blame.sh
@@ -113,8 +113,7 @@ test_expect_success 'set up abbrev tests' '
 '
 
 test_expect_success 'blame --abbrev=<n> works' '
-	# non-boundary commits get +1 for alignment
-	check_abbrev 31 --abbrev=30 HEAD &&
+	check_abbrev 30 --abbrev=30 HEAD &&
 	check_abbrev 30 --abbrev=30 ^HEAD
 '
 
@@ -141,10 +140,8 @@ test_expect_success 'blame --abbrev gets truncated with boundary commit' '
 '
 
 test_expect_success 'blame --abbrev -b truncates the blank boundary' '
-	# Note that `--abbrev=` always gets incremented by 1, which is why we
-	# expect 11 leading spaces and not 10.
 	cat >expect <<-EOF &&
-	$(printf "%11s" "") (<author@example.com> 2005-04-07 15:45:13 -0700 1) abbrev
+	$(printf "%10s" "") (<author@example.com> 2005-04-07 15:45:13 -0700 1) abbrev
 	EOF
 	git blame -b --abbrev=10 ^HEAD -- abbrev.t >actual &&
 	test_cmp expect actual
-- 
2.55.0

  parent reply	other threads:[~2026-07-06  8:38 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-06-30 11:15 git-blame vs. abbrev Laszlo Ersek
2026-06-30 19:49 ` Junio C Hamano
2026-06-30 20:32   ` Laszlo Ersek
2026-06-30 21:24     ` Junio C Hamano
2026-07-01  8:43       ` Laszlo Ersek
2026-06-30 21:38   ` René Scharfe
2026-07-01  8:45     ` Laszlo Ersek
2026-07-06  8:38 ` René Scharfe [this message]
2026-07-06 20:33   ` [PATCH] blame: reserve mark column only if necessary Junio C Hamano

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=92991b5e-0667-4315-89d5-1514a5499297@web.de \
    --to=l.s.r@web.de \
    --cc=git@vger.kernel.org \
    --cc=laszlo.ersek@posteo.net \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox