builtin/repo: add OID annotations to table output

The "structure" output for git-repo(1) does not show the corresponding OIDs for the largest objects in its "table" output. Update the output to include a list of OID annotations with an index to the corresponding row in the table. Signed-off-by: Justin Tobler <jltobler@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Justin Tobler committed Mar 2, 2026 at 15:45 UTC e00bb8c76e18357da3a2098cdac2a3c2c312c17d
2 files changed +143 -80
builtin/repo.c
+68 -10
@@ -238,6 +238,7 @@ struct repo_structure {
238
239 struct stats_table {
240 struct string_list rows;
241 + struct string_list annotations;
242
243 int name_col_width;
244 int value_col_width;
@@ -250,6 +251,8 @@ struct stats_table {
251 struct stats_table_entry {
252 char *value;
253 const char *unit;
254 + size_t index;
255 + struct object_id *oid;
256 };
257
258 static void stats_table_vaddf(struct stats_table *table,
@@ -272,6 +275,12 @@ static void stats_table_vaddf(struct stats_table *table,
275 table->name_col_width = name_width;
276 if (!entry)
277 return;
278 + if (entry->oid) {
279 + entry->index = table->annotations.nr + 1;
280 + strbuf_addf(&buf, "[%" PRIuMAX "] %s", (uintmax_t)entry->index,
281 + oid_to_hex(entry->oid));
282 + string_list_append_nodup(&table->annotations, strbuf_detach(&buf, NULL));
283 + }
284 if (entry->value) {
285 int value_width = utf8_strwidth(entry->value);
286 if (value_width > table->value_col_width)
@@ -282,6 +291,8 @@ static void stats_table_vaddf(struct stats_table *table,
291 if (unit_width > table->unit_col_width)
292 table->unit_col_width = unit_width;
293 }
294 +
295 + strbuf_release(&buf);
296 }
297
298 static void stats_table_addf(struct stats_table *table, const char *format, ...)
@@ -321,6 +332,27 @@ static void stats_table_size_addf(struct stats_table *table, size_t value,
332 va_end(ap);
333 }
334
335 +static void stats_table_object_size_addf(struct stats_table *table,
336 + struct object_id *oid, size_t value,
337 + const char *format, ...)
338 +{
339 + struct stats_table_entry *entry;
340 + va_list ap;
341 +
342 + CALLOC_ARRAY(entry, 1);
343 + humanise_bytes(value, &entry->value, &entry->unit, HUMANISE_COMPACT);
344 +
345 + /*
346 + * A NULL OID should not have a table annotation.
347 + */
348 + if (!is_null_oid(oid))
349 + entry->oid = oid;
350 +
351 + va_start(ap, format);
352 + stats_table_vaddf(table, entry, format, ap);
353 + va_end(ap);
354 +}
355 +
356 static inline size_t get_total_reference_count(struct ref_stats *stats)
357 {
358 return stats->branches + stats->remotes + stats->tags + stats->others;
@@ -389,19 +421,29 @@ static void stats_table_setup_structure(struct stats_table *table,
421 stats_table_addf(table, "");
422 stats_table_addf(table, "* %s", _("Largest objects"));
423 stats_table_addf(table, " * %s", _("Commits"));
392 - stats_table_size_addf(table, objects->largest.commit_size.value,
393 - " * %s", _("Maximum size"));
424 + stats_table_object_size_addf(table,
425 + &objects->largest.commit_size.oid,
426 + objects->largest.commit_size.value,
427 + " * %s", _("Maximum size"));
428 stats_table_addf(table, " * %s", _("Trees"));
395 - stats_table_size_addf(table, objects->largest.tree_size.value,
396 - " * %s", _("Maximum size"));
429 + stats_table_object_size_addf(table,
430 + &objects->largest.tree_size.oid,
431 + objects->largest.tree_size.value,
432 + " * %s", _("Maximum size"));
433 stats_table_addf(table, " * %s", _("Blobs"));
398 - stats_table_size_addf(table, objects->largest.blob_size.value,
399 - " * %s", _("Maximum size"));
434 + stats_table_object_size_addf(table,
435 + &objects->largest.blob_size.oid,
436 + objects->largest.blob_size.value,
437 + " * %s", _("Maximum size"));
438 stats_table_addf(table, " * %s", _("Tags"));
401 - stats_table_size_addf(table, objects->largest.tag_size.value,
402 - " * %s", _("Maximum size"));
439 + stats_table_object_size_addf(table,
440 + &objects->largest.tag_size.oid,
441 + objects->largest.tag_size.value,
442 + " * %s", _("Maximum size"));
443 }
444
445 +#define INDEX_WIDTH 4
446 +
447 static void stats_table_print_structure(const struct stats_table *table)
448 {
449 const char *name_col_title = _("Repository structure");
@@ -420,7 +462,8 @@ static void stats_table_print_structure(const struct stats_table *table)
462 value_col_width = title_value_width - unit_col_width;
463
464 strbuf_addstr(&buf, "| ");
423 - strbuf_utf8_align(&buf, ALIGN_LEFT, name_col_width, name_col_title);
465 + strbuf_utf8_align(&buf, ALIGN_LEFT, name_col_width + INDEX_WIDTH,
466 + name_col_title);
467 strbuf_addstr(&buf, " | ");
468 strbuf_utf8_align(&buf, ALIGN_LEFT,
469 value_col_width + unit_col_width + 1, value_col_title);
@@ -428,7 +471,7 @@ static void stats_table_print_structure(const struct stats_table *table)
471 printf("%s\n", buf.buf);
472
473 printf("| ");
431 - for (int i = 0; i < name_col_width; i++)
474 + for (int i = 0; i < name_col_width + INDEX_WIDTH; i++)
475 putchar('-');
476 printf(" | ");
477 for (int i = 0; i < value_col_width + unit_col_width + 1; i++)
@@ -450,6 +493,13 @@ static void stats_table_print_structure(const struct stats_table *table)
493 strbuf_reset(&buf);
494 strbuf_addstr(&buf, "| ");
495 strbuf_utf8_align(&buf, ALIGN_LEFT, name_col_width, item->string);
496 +
497 + if (entry && entry->oid)
498 + strbuf_addf(&buf, " [%" PRIuMAX "]",
499 + (uintmax_t)entry->index);
500 + else
501 + strbuf_addchars(&buf, ' ', INDEX_WIDTH);
502 +
503 strbuf_addstr(&buf, " | ");
504 strbuf_utf8_align(&buf, ALIGN_RIGHT, value_col_width, value);
505 strbuf_addch(&buf, ' ');
@@ -458,6 +508,12 @@ static void stats_table_print_structure(const struct stats_table *table)
508 printf("%s\n", buf.buf);
509 }
510
511 + if (table->annotations.nr) {
512 + printf("\n");
513 + for_each_string_list_item(item, &table->annotations)
514 + printf("%s\n", item->string);
515 + }
516 +
517 strbuf_release(&buf);
518 }
519
@@ -473,6 +529,7 @@ static void stats_table_clear(struct stats_table *table)
529 }
530
531 string_list_clear(&table->rows, 1);
532 + string_list_clear(&table->annotations, 1);
533 }
534
535 static inline void print_keyvalue(const char *key, char key_delim, size_t value,
@@ -702,6 +759,7 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
759 {
760 struct stats_table table = {
761 .rows = STRING_LIST_INIT_DUP,
762 + .annotations = STRING_LIST_INIT_DUP,
763 };
764 enum output_format format = FORMAT_TABLE;
765 struct repo_structure stats = { 0 };
t/t1901-repo-structure.sh
+75 -70
@@ -27,41 +27,41 @@ test_expect_success 'empty repository' '
27 (
28 cd repo &&
29 cat >expect <<-\EOF &&
30 - | Repository structure | Value |
31 - | -------------------- | ------ |
32 - | * References | |
33 - | * Count | 0 |
34 - | * Branches | 0 |
35 - | * Tags | 0 |
36 - | * Remotes | 0 |
37 - | * Others | 0 |
38 - | | |
39 - | * Reachable objects | |
40 - | * Count | 0 |
41 - | * Commits | 0 |
42 - | * Trees | 0 |
43 - | * Blobs | 0 |
44 - | * Tags | 0 |
45 - | * Inflated size | 0 B |
46 - | * Commits | 0 B |
47 - | * Trees | 0 B |
48 - | * Blobs | 0 B |
49 - | * Tags | 0 B |
50 - | * Disk size | 0 B |
51 - | * Commits | 0 B |
52 - | * Trees | 0 B |
53 - | * Blobs | 0 B |
54 - | * Tags | 0 B |
55 - | | |
56 - | * Largest objects | |
57 - | * Commits | |
58 - | * Maximum size | 0 B |
59 - | * Trees | |
60 - | * Maximum size | 0 B |
61 - | * Blobs | |
62 - | * Maximum size | 0 B |
63 - | * Tags | |
64 - | * Maximum size | 0 B |
30 + | Repository structure | Value |
31 + | ------------------------ | ------ |
32 + | * References | |
33 + | * Count | 0 |
34 + | * Branches | 0 |
35 + | * Tags | 0 |
36 + | * Remotes | 0 |
37 + | * Others | 0 |
38 + | | |
39 + | * Reachable objects | |
40 + | * Count | 0 |
41 + | * Commits | 0 |
42 + | * Trees | 0 |
43 + | * Blobs | 0 |
44 + | * Tags | 0 |
45 + | * Inflated size | 0 B |
46 + | * Commits | 0 B |
47 + | * Trees | 0 B |
48 + | * Blobs | 0 B |
49 + | * Tags | 0 B |
50 + | * Disk size | 0 B |
51 + | * Commits | 0 B |
52 + | * Trees | 0 B |
53 + | * Blobs | 0 B |
54 + | * Tags | 0 B |
55 + | | |
56 + | * Largest objects | |
57 + | * Commits | |
58 + | * Maximum size | 0 B |
59 + | * Trees | |
60 + | * Maximum size | 0 B |
61 + | * Blobs | |
62 + | * Maximum size | 0 B |
63 + | * Tags | |
64 + | * Maximum size | 0 B |
65 EOF
66
67 git repo structure >out 2>err &&
@@ -89,41 +89,46 @@ test_expect_success SHA1 'repository with references and objects' '
89 # git-rev-list(1) --disk-usage=human option printing the full
90 # "byte/bytes" unit string instead of just "B".
91 cat >expect <<-EOF &&
92 - | Repository structure | Value |
93 - | -------------------- | ---------- |
94 - | * References | |
95 - | * Count | 4 |
96 - | * Branches | 1 |
97 - | * Tags | 1 |
98 - | * Remotes | 1 |
99 - | * Others | 1 |
100 - | | |
101 - | * Reachable objects | |
102 - | * Count | 3.02 k |
103 - | * Commits | 1.01 k |
104 - | * Trees | 1.01 k |
105 - | * Blobs | 1.01 k |
106 - | * Tags | 1 |
107 - | * Inflated size | 16.03 MiB |
108 - | * Commits | 217.92 KiB |
109 - | * Trees | 15.81 MiB |
110 - | * Blobs | 11.68 KiB |
111 - | * Tags | 132 B |
112 - | * Disk size | $(object_type_disk_usage all true) |
113 - | * Commits | $(object_type_disk_usage commit true) |
114 - | * Trees | $(object_type_disk_usage tree true) |
115 - | * Blobs | $(object_type_disk_usage blob true) |
116 - | * Tags | $(object_type_disk_usage tag) B |
117 - | | |
118 - | * Largest objects | |
119 - | * Commits | |
120 - | * Maximum size | 223 B |
121 - | * Trees | |
122 - | * Maximum size | 32.29 KiB |
123 - | * Blobs | |
124 - | * Maximum size | 13 B |
125 - | * Tags | |
126 - | * Maximum size | 132 B |
92 + | Repository structure | Value |
93 + | ------------------------ | ---------- |
94 + | * References | |
95 + | * Count | 4 |
96 + | * Branches | 1 |
97 + | * Tags | 1 |
98 + | * Remotes | 1 |
99 + | * Others | 1 |
100 + | | |
101 + | * Reachable objects | |
102 + | * Count | 3.02 k |
103 + | * Commits | 1.01 k |
104 + | * Trees | 1.01 k |
105 + | * Blobs | 1.01 k |
106 + | * Tags | 1 |
107 + | * Inflated size | 16.03 MiB |
108 + | * Commits | 217.92 KiB |
109 + | * Trees | 15.81 MiB |
110 + | * Blobs | 11.68 KiB |
111 + | * Tags | 132 B |
112 + | * Disk size | $(object_type_disk_usage all true) |
113 + | * Commits | $(object_type_disk_usage commit true) |
114 + | * Trees | $(object_type_disk_usage tree true) |
115 + | * Blobs | $(object_type_disk_usage blob true) |
116 + | * Tags | $(object_type_disk_usage tag) B |
117 + | | |
118 + | * Largest objects | |
119 + | * Commits | |
120 + | * Maximum size [1] | 223 B |
121 + | * Trees | |
122 + | * Maximum size [2] | 32.29 KiB |
123 + | * Blobs | |
124 + | * Maximum size [3] | 13 B |
125 + | * Tags | |
126 + | * Maximum size [4] | 132 B |
127 +
128 + [1] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a
129 + [2] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c
130 + [3] 97d808e45116bf02103490294d3d46dad7a2ac62
131 + [4] 4dae4f5954f5e6feb3577cfb1b181daa3fd3afd2
132 EOF
133
134 git repo structure >out 2>err &&