blame: reserve mark column only if necessary
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> Signed-off-by: Junio C Hamano <gitster@pobox.com>
René Scharfe committed
Jul 6, 2026 at 10:38 UTC
c3df27f347dcefc14629c7afb178420762d45eef
3 files changed
+53
-33
Documentation/git-blame.adoc
+6
-5
@@ -88,11 +88,12 @@ include::blame-options.adoc[]
88
include::diff-algorithm-option.adoc[]
89
90
`--abbrev=<n>`::
91
- Instead of using the default _7+1_ hexadecimal digits as the
92
- abbreviated object name, use _<m>+1_ digits, where _<m>_ is at
93
- least _<n>_ but ensures the commit object names are unique.
94
- Note that 1 column
95
- is used for a caret to mark the boundary commit.
91
+ Instead of using the default _7_ hexadecimal digits as the
92
+ abbreviated object name, use at least _<n>_ digits, but ensure
93
+ the commit object names are unique.
94
+ If commits marked with caret (boundary), question mark (ignored)
95
+ or asterisk (unblamable) are shown, extend unmarked object names
96
+ to align them.
97
98
99
THE DEFAULT FORMAT
builtin/blame.c
+45
-23
@@ -453,6 +453,36 @@ static void determine_line_heat(struct commit_info *ci, const char **dest_color)
453
*dest_color = colorfield[i].col;
454
}
455
456
+static inline int maybe_putc(int c, FILE *out)
457
+{
458
+ return out ? putc(c, out) : 0;
459
+}
460
+
461
+static size_t print_marks(FILE *out, const struct blame_entry *ent, int opt)
462
+{
463
+ size_t len = 0;
464
+
465
+ if ((ent->suspect->commit->object.flags & UNINTERESTING) &&
466
+ !blank_boundary && !(opt & OUTPUT_ANNOTATE_COMPAT)) {
467
+ maybe_putc('^', out);
468
+ len++;
469
+ }
470
+ if (mark_unblamable_lines && ent->unblamable) {
471
+ maybe_putc('*', out);
472
+ len++;
473
+ }
474
+ if (mark_ignored_lines && ent->ignored) {
475
+ maybe_putc('?', out);
476
+ len++;
477
+ }
478
+ return len;
479
+}
480
+
481
+static size_t count_marks(const struct blame_entry *ent, int opt)
482
+{
483
+ return print_marks(NULL, ent, opt);
484
+}
485
+
486
static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent,
487
int opt, struct blame_entry *prev_ent)
488
{
@@ -499,23 +529,10 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent,
529
if (color)
530
fputs(color, stdout);
531
502
- if (suspect->commit->object.flags & UNINTERESTING) {
503
- if (blank_boundary) {
504
- memset(hex, ' ', strlen(hex));
505
- } else if (!(opt & OUTPUT_ANNOTATE_COMPAT)) {
506
- length--;
507
- putchar('^');
508
- }
509
- }
510
-
511
- if (mark_unblamable_lines && ent->unblamable) {
512
- length--;
513
- putchar('*');
514
- }
515
- if (mark_ignored_lines && ent->ignored) {
516
- length--;
517
- putchar('?');
518
- }
532
+ if ((suspect->commit->object.flags & UNINTERESTING) &&
533
+ blank_boundary)
534
+ memset(hex, ' ', strlen(hex));
535
+ length -= print_marks(stdout, ent, opt);
536
537
printf("%.*s", (int)(length < GIT_MAX_HEXSZ ? length : GIT_MAX_HEXSZ), hex);
538
if (opt & OUTPUT_ANNOTATE_COMPAT) {
@@ -647,11 +664,15 @@ static void find_alignment(struct blame_scoreboard *sb, int *option)
664
struct blame_entry *e;
665
int compute_auto_abbrev = (abbrev < 0);
666
int auto_abbrev = DEFAULT_ABBREV;
667
+ size_t max_marks_count = 0;
668
669
for (e = sb->ent; e; e = e->next) {
670
struct blame_origin *suspect = e->suspect;
671
int num;
672
+ size_t marks_count = count_marks(e, *option);
673
674
+ if (max_marks_count < marks_count)
675
+ max_marks_count = marks_count;
676
if (compute_auto_abbrev)
677
auto_abbrev = update_auto_abbrev(auto_abbrev, suspect);
678
if (strcmp(suspect->path, sb->path))
@@ -685,8 +706,12 @@ static void find_alignment(struct blame_scoreboard *sb, int *option)
706
max_score_digits = decimal_width(largest_score);
707
708
if (compute_auto_abbrev)
688
- /* one more abbrev length is needed for the boundary commit */
689
- abbrev = auto_abbrev + 1;
709
+ abbrev = auto_abbrev;
710
+ if (abbrev < (int)the_hash_algo->hexsz) {
711
+ abbrev += max_marks_count;
712
+ if (abbrev > (int)the_hash_algo->hexsz)
713
+ abbrev = the_hash_algo->hexsz;
714
+ }
715
}
716
717
static void sanity_check_on_fail(struct blame_scoreboard *sb, int baa)
@@ -1047,10 +1072,7 @@ parse_done:
1072
} else if (show_progress < 0)
1073
show_progress = isatty(2);
1074
1050
- if (0 < abbrev && abbrev < (int)the_hash_algo->hexsz)
1051
- /* one more abbrev length is needed for the boundary commit */
1052
- abbrev++;
1053
- else if (!abbrev)
1075
+ if (!abbrev)
1076
abbrev = the_hash_algo->hexsz;
1077
1078
if (revs_file && read_ancestry(revs_file))
t/t8002-blame.sh
+2
-5
@@ -113,8 +113,7 @@ test_expect_success 'set up abbrev tests' '
113
'
114
115
test_expect_success 'blame --abbrev=<n> works' '
116
- # non-boundary commits get +1 for alignment
117
- check_abbrev 31 --abbrev=30 HEAD &&
116
+ check_abbrev 30 --abbrev=30 HEAD &&
117
check_abbrev 30 --abbrev=30 ^HEAD
118
'
119
@@ -141,10 +140,8 @@ test_expect_success 'blame --abbrev gets truncated with boundary commit' '
140
'
141
142
test_expect_success 'blame --abbrev -b truncates the blank boundary' '
144
- # Note that `--abbrev=` always gets incremented by 1, which is why we
145
- # expect 11 leading spaces and not 10.
143
cat >expect <<-EOF &&
147
- $(printf "%11s" "") (<author@example.com> 2005-04-07 15:45:13 -0700 1) abbrev
144
+ $(printf "%10s" "") (<author@example.com> 2005-04-07 15:45:13 -0700 1) abbrev
145
EOF
146
git blame -b --abbrev=10 ^HEAD -- abbrev.t >actual &&
147
test_cmp expect actual