builtin/repo: collect largest inflated objects

The "structure" output for git-repo(1) shows the total inflated and disk sizes of reachable objects in the repository, but doesn't show the size of the largest individual objects. Since an individual object may be a large contributor to the overall repository size, it is useful for users to know the maximum size of individual objects. While interating across objects, record the size and OID of the largest objects encountered for each object type to provide as output. Note that the default "table" output format only displays size information and not the corresponding OID. In a subsequent commit, the table format is updated to add table annotations that mention the OID. 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 e33ac9cc9e819f9de8ffe25c165393514cc61b12
3 files changed +92
Documentation/git-repo.adoc
+1
@@ -52,6 +52,7 @@ supported:
52 * Reachable object counts categorized by type
53 * Total inflated size of reachable objects by type
54 * Total disk size of reachable objects by type
55 +* Largest reachable objects in the repository by type
56 +
57 The output format can be chosen through the flag `--format`. Three formats are
58 supported:
builtin/repo.c
+63
@@ -2,6 +2,7 @@
2
3 #include "builtin.h"
4 #include "environment.h"
5 +#include "hash.h"
6 #include "hex.h"
7 #include "odb.h"
8 #include "parse-options.h"
@@ -197,6 +198,18 @@ static int cmd_repo_info(int argc, const char **argv, const char *prefix,
198 return print_fields(argc, argv, repo, format);
199 }
200
201 +struct object_data {
202 + struct object_id oid;
203 + size_t value;
204 +};
205 +
206 +struct largest_objects {
207 + struct object_data tag_size;
208 + struct object_data commit_size;
209 + struct object_data tree_size;
210 + struct object_data blob_size;
211 +};
212 +
213 struct ref_stats {
214 size_t branches;
215 size_t remotes;
@@ -215,6 +228,7 @@ struct object_stats {
228 struct object_values type_counts;
229 struct object_values inflated_sizes;
230 struct object_values disk_sizes;
231 + struct largest_objects largest;
232 };
233
234 struct repo_structure {
@@ -371,6 +385,21 @@ static void stats_table_setup_structure(struct stats_table *table,
385 " * %s", _("Blobs"));
386 stats_table_size_addf(table, objects->disk_sizes.tags,
387 " * %s", _("Tags"));
388 +
389 + stats_table_addf(table, "");
390 + stats_table_addf(table, "* %s", _("Largest objects"));
391 + stats_table_addf(table, " * %s", _("Commits"));
392 + stats_table_size_addf(table, objects->largest.commit_size.value,
393 + " * %s", _("Maximum size"));
394 + stats_table_addf(table, " * %s", _("Trees"));
395 + stats_table_size_addf(table, objects->largest.tree_size.value,
396 + " * %s", _("Maximum size"));
397 + stats_table_addf(table, " * %s", _("Blobs"));
398 + stats_table_size_addf(table, objects->largest.blob_size.value,
399 + " * %s", _("Maximum size"));
400 + stats_table_addf(table, " * %s", _("Tags"));
401 + stats_table_size_addf(table, objects->largest.tag_size.value,
402 + " * %s", _("Maximum size"));
403 }
404
405 static void stats_table_print_structure(const struct stats_table *table)
@@ -453,6 +482,14 @@ static inline void print_keyvalue(const char *key, char key_delim, size_t value,
482 value_delim);
483 }
484
485 +static void print_object_data(const char *key, char key_delim,
486 + struct object_data *data, char value_delim)
487 +{
488 + print_keyvalue(key, key_delim, data->value, value_delim);
489 + printf("%s_oid%c%s%c", key, key_delim, oid_to_hex(&data->oid),
490 + value_delim);
491 +}
492 +
493 static void structure_keyvalue_print(struct repo_structure *stats,
494 char key_delim, char value_delim)
495 {
@@ -492,6 +529,15 @@ static void structure_keyvalue_print(struct repo_structure *stats,
529 print_keyvalue("objects.tags.disk_size", key_delim,
530 stats->objects.disk_sizes.tags, value_delim);
531
532 + print_object_data("objects.commits.max_size", key_delim,
533 + &stats->objects.largest.commit_size, value_delim);
534 + print_object_data("objects.trees.max_size", key_delim,
535 + &stats->objects.largest.tree_size, value_delim);
536 + print_object_data("objects.blobs.max_size", key_delim,
537 + &stats->objects.largest.blob_size, value_delim);
538 + print_object_data("objects.tags.max_size", key_delim,
539 + &stats->objects.largest.tag_size, value_delim);
540 +
541 fflush(stdout);
542 }
543
@@ -560,6 +606,15 @@ struct count_objects_data {
606 struct progress *progress;
607 };
608
609 +static void check_largest(struct object_data *data, struct object_id *oid,
610 + size_t value)
611 +{
612 + if (value > data->value || is_null_oid(&data->oid)) {
613 + oidcpy(&data->oid, oid);
614 + data->value = value;
615 + }
616 +}
617 +
618 static int count_objects(const char *path UNUSED, struct oid_array *oids,
619 enum object_type type, void *cb_data)
620 {
@@ -585,21 +640,29 @@ static int count_objects(const char *path UNUSED, struct oid_array *oids,
640 stats->type_counts.tags++;
641 stats->inflated_sizes.tags += inflated;
642 stats->disk_sizes.tags += disk;
643 + check_largest(&stats->largest.tag_size, &oids->oid[i],
644 + inflated);
645 break;
646 case OBJ_COMMIT:
647 stats->type_counts.commits++;
648 stats->inflated_sizes.commits += inflated;
649 stats->disk_sizes.commits += disk;
650 + check_largest(&stats->largest.commit_size, &oids->oid[i],
651 + inflated);
652 break;
653 case OBJ_TREE:
654 stats->type_counts.trees++;
655 stats->inflated_sizes.trees += inflated;
656 stats->disk_sizes.trees += disk;
657 + check_largest(&stats->largest.tree_size, &oids->oid[i],
658 + inflated);
659 break;
660 case OBJ_BLOB:
661 stats->type_counts.blobs++;
662 stats->inflated_sizes.blobs += inflated;
663 stats->disk_sizes.blobs += disk;
664 + check_largest(&stats->largest.blob_size, &oids->oid[i],
665 + inflated);
666 break;
667 default:
668 BUG("invalid object type");
t/t1901-repo-structure.sh
+28
@@ -52,6 +52,16 @@ test_expect_success 'empty repository' '
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 &&
@@ -104,6 +114,16 @@ test_expect_success SHA1 'repository with references and objects' '
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 |
127 EOF
128
129 git repo structure >out 2>err &&
@@ -138,6 +158,14 @@ test_expect_success SHA1 'keyvalue and nul format' '
158 objects.trees.disk_size=$(object_type_disk_usage tree)
159 objects.blobs.disk_size=$(object_type_disk_usage blob)
160 objects.tags.disk_size=$(object_type_disk_usage tag)
161 + objects.commits.max_size=221
162 + objects.commits.max_size_oid=de3508174b5c2ace6993da67cae9be9069e2df39
163 + objects.trees.max_size=1335
164 + objects.trees.max_size_oid=09931deea9d81ec21300d3e13c74412f32eacec5
165 + objects.blobs.max_size=11
166 + objects.blobs.max_size_oid=eaeeedced46482bd4281fda5a5f05ce24854151f
167 + objects.tags.max_size=132
168 + objects.tags.max_size_oid=1ee0f2b16ea37d895dbe9dbd76cd2ac70446176c
169 EOF
170
171 git repo structure --format=keyvalue >out 2>err &&