builtin/repo: humanise count values in structure output

The table output format for the git-repo(1) structure subcommand is used by default and intended to provide output to users in a human-friendly manner. When the reference/object count values in a repository are large, it becomes more cumbersome for users to read the values. For larger values, update the table output format to instead produce more human-friendly count values that are scaled down with the appropriate unit prefix. Output for the keyvalue and nul formats remains unchanged. Signed-off-by: Justin Tobler <jltobler@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Justin Tobler committed Dec 17, 2025 at 11:54 UTC 54731320cc3db337f9a3e3920f707e9de3596c60
4 files changed +91 -41
builtin/repo.c
+28 -10
@@ -223,6 +223,7 @@ struct stats_table {
223
224 int name_col_width;
225 int value_col_width;
226 + int unit_col_width;
227 };
228
229 /*
@@ -230,6 +231,7 @@ struct stats_table {
231 */
232 struct stats_table_entry {
233 char *value;
234 + const char *unit;
235 };
236
237 static void stats_table_vaddf(struct stats_table *table,
@@ -250,11 +252,18 @@ static void stats_table_vaddf(struct stats_table *table,
252
253 if (name_width > table->name_col_width)
254 table->name_col_width = name_width;
253 - if (entry) {
255 + if (!entry)
256 + return;
257 + if (entry->value) {
258 int value_width = utf8_strwidth(entry->value);
259 if (value_width > table->value_col_width)
260 table->value_col_width = value_width;
261 }
262 + if (entry->unit) {
263 + int unit_width = utf8_strwidth(entry->unit);
264 + if (unit_width > table->unit_col_width)
265 + table->unit_col_width = unit_width;
266 + }
267 }
268
269 static void stats_table_addf(struct stats_table *table, const char *format, ...)
@@ -273,7 +282,7 @@ static void stats_table_count_addf(struct stats_table *table, size_t value,
282 va_list ap;
283
284 CALLOC_ARRAY(entry, 1);
276 - entry->value = xstrfmt("%" PRIuMAX, (uintmax_t)value);
285 + humanise_count(value, &entry->value, &entry->unit);
286
287 va_start(ap, format);
288 stats_table_vaddf(table, entry, format, ap);
@@ -324,20 +333,24 @@ static void stats_table_print_structure(const struct stats_table *table)
333 {
334 const char *name_col_title = _("Repository structure");
335 const char *value_col_title = _("Value");
327 - int name_col_width = utf8_strwidth(name_col_title);
328 - int value_col_width = utf8_strwidth(value_col_title);
336 + int title_name_width = utf8_strwidth(name_col_title);
337 + int title_value_width = utf8_strwidth(value_col_title);
338 + int name_col_width = table->name_col_width;
339 + int value_col_width = table->value_col_width;
340 + int unit_col_width = table->unit_col_width;
341 struct string_list_item *item;
342 struct strbuf buf = STRBUF_INIT;
343
332 - if (table->name_col_width > name_col_width)
333 - name_col_width = table->name_col_width;
334 - if (table->value_col_width > value_col_width)
335 - value_col_width = table->value_col_width;
344 + if (title_name_width > name_col_width)
345 + name_col_width = title_name_width;
346 + if (title_value_width > value_col_width + unit_col_width + 1)
347 + value_col_width = title_value_width - unit_col_width;
348
349 strbuf_addstr(&buf, "| ");
350 strbuf_utf8_align(&buf, ALIGN_LEFT, name_col_width, name_col_title);
351 strbuf_addstr(&buf, " | ");
340 - strbuf_utf8_align(&buf, ALIGN_LEFT, value_col_width, value_col_title);
352 + strbuf_utf8_align(&buf, ALIGN_LEFT,
353 + value_col_width + unit_col_width + 1, value_col_title);
354 strbuf_addstr(&buf, " |");
355 printf("%s\n", buf.buf);
356
@@ -345,17 +358,20 @@ static void stats_table_print_structure(const struct stats_table *table)
358 for (int i = 0; i < name_col_width; i++)
359 putchar('-');
360 printf(" | ");
348 - for (int i = 0; i < value_col_width; i++)
361 + for (int i = 0; i < value_col_width + unit_col_width + 1; i++)
362 putchar('-');
363 printf(" |\n");
364
365 for_each_string_list_item(item, &table->rows) {
366 struct stats_table_entry *entry = item->util;
367 const char *value = "";
368 + const char *unit = "";
369
370 if (entry) {
371 struct stats_table_entry *entry = item->util;
372 value = entry->value;
373 + if (entry->unit)
374 + unit = entry->unit;
375 }
376
377 strbuf_reset(&buf);
@@ -363,6 +379,8 @@ static void stats_table_print_structure(const struct stats_table *table)
379 strbuf_utf8_align(&buf, ALIGN_LEFT, name_col_width, item->string);
380 strbuf_addstr(&buf, " | ");
381 strbuf_utf8_align(&buf, ALIGN_RIGHT, value_col_width, value);
382 + strbuf_addch(&buf, ' ');
383 + strbuf_utf8_align(&buf, ALIGN_LEFT, unit_col_width, unit);
384 strbuf_addstr(&buf, " |");
385 printf("%s\n", buf.buf);
386 }
strbuf.c
+26
@@ -836,6 +836,32 @@ void strbuf_addstr_urlencode(struct strbuf *sb, const char *s,
836 strbuf_add_urlencode(sb, s, strlen(s), allow_unencoded_fn);
837 }
838
839 +void humanise_count(size_t count, char **value, const char **unit)
840 +{
841 + if (count >= 1000000000) {
842 + size_t x = count + 5000000; /* for rounding */
843 + *value = xstrfmt(_("%u.%2.2u"), (unsigned)(x / 1000000000),
844 + (unsigned)(x % 1000000000 / 10000000));
845 + /* TRANSLATORS: SI decimal prefix symbol for 10^9 */
846 + *unit = _("G");
847 + } else if (count >= 1000000) {
848 + size_t x = count + 5000; /* for rounding */
849 + *value = xstrfmt(_("%u.%2.2u"), (unsigned)(x / 1000000),
850 + (unsigned)(x % 1000000 / 10000));
851 + /* TRANSLATORS: SI decimal prefix symbol for 10^6 */
852 + *unit = _("M");
853 + } else if (count >= 1000) {
854 + size_t x = count + 5; /* for rounding */
855 + *value = xstrfmt(_("%u.%2.2u"), (unsigned)(x / 1000),
856 + (unsigned)(x % 1000 / 10));
857 + /* TRANSLATORS: SI decimal prefix symbol for 10^3 */
858 + *unit = _("k");
859 + } else {
860 + *value = xstrfmt("%u", (unsigned)count);
861 + *unit = NULL;
862 + }
863 +}
864 +
865 void humanise_bytes(off_t bytes, char **value, const char **unit,
866 unsigned flags)
867 {
strbuf.h
+6
@@ -381,6 +381,12 @@ enum humanise_flags {
381 void humanise_bytes(off_t bytes, char **value, const char **unit,
382 unsigned flags);
383
384 +/**
385 + * Converts the given count into a downscaled human-readable value and
386 + * corresponding unit as two separate strings.
387 + */
388 +void humanise_count(size_t count, char **value, const char **unit);
389 +
390 /**
391 * Append the given byte size as a human-readable string (i.e. 12.23 KiB,
392 * 3.50 MiB).
t/t1901-repo-structure.sh
+31 -31
@@ -10,21 +10,21 @@ test_expect_success 'empty repository' '
10 (
11 cd repo &&
12 cat >expect <<-\EOF &&
13 - | Repository structure | Value |
14 - | -------------------- | ----- |
15 - | * References | |
16 - | * Count | 0 |
17 - | * Branches | 0 |
18 - | * Tags | 0 |
19 - | * Remotes | 0 |
20 - | * Others | 0 |
21 - | | |
22 - | * Reachable objects | |
23 - | * Count | 0 |
24 - | * Commits | 0 |
25 - | * Trees | 0 |
26 - | * Blobs | 0 |
27 - | * Tags | 0 |
13 + | Repository structure | Value |
14 + | -------------------- | ------ |
15 + | * References | |
16 + | * Count | 0 |
17 + | * Branches | 0 |
18 + | * Tags | 0 |
19 + | * Remotes | 0 |
20 + | * Others | 0 |
21 + | | |
22 + | * Reachable objects | |
23 + | * Count | 0 |
24 + | * Commits | 0 |
25 + | * Trees | 0 |
26 + | * Blobs | 0 |
27 + | * Tags | 0 |
28 EOF
29
30 git repo structure >out 2>err &&
@@ -39,7 +39,7 @@ test_expect_success 'repository with references and objects' '
39 git init repo &&
40 (
41 cd repo &&
42 - test_commit_bulk 42 &&
42 + test_commit_bulk 1005 &&
43 git tag -a foo -m bar &&
44
45 oid="$(git rev-parse HEAD)" &&
@@ -49,21 +49,21 @@ test_expect_success 'repository with references and objects' '
49 git notes add -m foo &&
50
51 cat >expect <<-\EOF &&
52 - | Repository structure | Value |
53 - | -------------------- | ----- |
54 - | * References | |
55 - | * Count | 4 |
56 - | * Branches | 1 |
57 - | * Tags | 1 |
58 - | * Remotes | 1 |
59 - | * Others | 1 |
60 - | | |
61 - | * Reachable objects | |
62 - | * Count | 130 |
63 - | * Commits | 43 |
64 - | * Trees | 43 |
65 - | * Blobs | 43 |
66 - | * Tags | 1 |
52 + | Repository structure | Value |
53 + | -------------------- | ------ |
54 + | * References | |
55 + | * Count | 4 |
56 + | * Branches | 1 |
57 + | * Tags | 1 |
58 + | * Remotes | 1 |
59 + | * Others | 1 |
60 + | | |
61 + | * Reachable objects | |
62 + | * Count | 3.02 k |
63 + | * Commits | 1.01 k |
64 + | * Trees | 1.01 k |
65 + | * Blobs | 1.01 k |
66 + | * Tags | 1 |
67 EOF
68
69 git repo structure >out 2>err &&