builtin/repo: add object counts in structure output
The amount of objects in a repository can provide insight regarding its shape. To surface this information, use the path-walk API to count the number of reachable objects in the repository by object type. All regular references are used to determine the reachable set of objects. The object counts are appended to the same table containing the reference information. Signed-off-by: Justin Tobler <jltobler@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Justin Tobler committed
Oct 21, 2025 at 13:25 UTC
eb5cf58ffcd4bb117c870d448b0df0193df52c82
3 files changed
+117
-8
Documentation/git-repo.adoc
+1
@@ -49,6 +49,7 @@ supported:
49
following kinds of information are reported:
50
+
51
* Reference counts categorized by type
52
+* Reachable object counts categorized by type
53
54
+
55
The table output format may change and is not intended for machine parsing.
builtin/repo.c
+99
-6
@@ -3,9 +3,11 @@
3
#include "builtin.h"
4
#include "environment.h"
5
#include "parse-options.h"
6
+#include "path-walk.h"
7
#include "quote.h"
8
#include "ref-filter.h"
9
#include "refs.h"
10
+#include "revision.h"
11
#include "strbuf.h"
12
#include "string-list.h"
13
#include "shallow.h"
@@ -167,6 +169,18 @@ struct ref_stats {
169
size_t others;
170
};
171
172
+struct object_stats {
173
+ size_t tags;
174
+ size_t commits;
175
+ size_t trees;
176
+ size_t blobs;
177
+};
178
+
179
+struct repo_structure {
180
+ struct ref_stats refs;
181
+ struct object_stats objects;
182
+};
183
+
184
struct stats_table {
185
struct string_list rows;
186
@@ -234,9 +248,17 @@ static inline size_t get_total_reference_count(struct ref_stats *stats)
248
return stats->branches + stats->remotes + stats->tags + stats->others;
249
}
250
251
+static inline size_t get_total_object_count(struct object_stats *stats)
252
+{
253
+ return stats->tags + stats->commits + stats->trees + stats->blobs;
254
+}
255
+
256
static void stats_table_setup_structure(struct stats_table *table,
238
- struct ref_stats *refs)
257
+ struct repo_structure *stats)
258
{
259
+ struct object_stats *objects = &stats->objects;
260
+ struct ref_stats *refs = &stats->refs;
261
+ size_t object_total;
262
size_t ref_total;
263
264
ref_total = get_total_reference_count(refs);
@@ -246,6 +268,15 @@ static void stats_table_setup_structure(struct stats_table *table,
268
stats_table_count_addf(table, refs->tags, " * %s", _("Tags"));
269
stats_table_count_addf(table, refs->remotes, " * %s", _("Remotes"));
270
stats_table_count_addf(table, refs->others, " * %s", _("Others"));
271
+
272
+ object_total = get_total_object_count(objects);
273
+ stats_table_addf(table, "");
274
+ stats_table_addf(table, "* %s", _("Reachable objects"));
275
+ stats_table_count_addf(table, object_total, " * %s", _("Count"));
276
+ stats_table_count_addf(table, objects->commits, " * %s", _("Commits"));
277
+ stats_table_count_addf(table, objects->trees, " * %s", _("Trees"));
278
+ stats_table_count_addf(table, objects->blobs, " * %s", _("Blobs"));
279
+ stats_table_count_addf(table, objects->tags, " * %s", _("Tags"));
280
}
281
282
static void stats_table_print_structure(const struct stats_table *table)
@@ -299,12 +330,18 @@ static void stats_table_clear(struct stats_table *table)
330
string_list_clear(&table->rows, 1);
331
}
332
333
+struct count_references_data {
334
+ struct ref_stats *stats;
335
+ struct rev_info *revs;
336
+};
337
+
338
static int count_references(const char *refname,
339
const char *referent UNUSED,
304
- const struct object_id *oid UNUSED,
340
+ const struct object_id *oid,
341
int flags UNUSED, void *cb_data)
342
{
307
- struct ref_stats *stats = cb_data;
343
+ struct count_references_data *data = cb_data;
344
+ struct ref_stats *stats = data->stats;
345
346
switch (ref_kind_from_refname(refname)) {
347
case FILTER_REFS_BRANCHES:
@@ -323,13 +360,64 @@ static int count_references(const char *refname,
360
BUG("unexpected reference type");
361
}
362
363
+ /*
364
+ * While iterating through references for counting, also add OIDs in
365
+ * preparation for the path walk.
366
+ */
367
+ add_pending_oid(data->revs, NULL, oid, 0);
368
+
369
return 0;
370
}
371
372
static void structure_count_references(struct ref_stats *stats,
373
+ struct rev_info *revs,
374
struct repository *repo)
375
{
332
- refs_for_each_ref(get_main_ref_store(repo), count_references, &stats);
376
+ struct count_references_data data = {
377
+ .stats = stats,
378
+ .revs = revs,
379
+ };
380
+
381
+ refs_for_each_ref(get_main_ref_store(repo), count_references, &data);
382
+}
383
+
384
+
385
+static int count_objects(const char *path UNUSED, struct oid_array *oids,
386
+ enum object_type type, void *cb_data)
387
+{
388
+ struct object_stats *stats = cb_data;
389
+
390
+ switch (type) {
391
+ case OBJ_TAG:
392
+ stats->tags += oids->nr;
393
+ break;
394
+ case OBJ_COMMIT:
395
+ stats->commits += oids->nr;
396
+ break;
397
+ case OBJ_TREE:
398
+ stats->trees += oids->nr;
399
+ break;
400
+ case OBJ_BLOB:
401
+ stats->blobs += oids->nr;
402
+ break;
403
+ default:
404
+ BUG("invalid object type");
405
+ }
406
+
407
+ return 0;
408
+}
409
+
410
+static void structure_count_objects(struct object_stats *stats,
411
+ struct rev_info *revs)
412
+{
413
+ struct path_walk_info info = PATH_WALK_INFO_INIT;
414
+
415
+ info.revs = revs;
416
+ info.path_fn = count_objects;
417
+ info.path_fn_data = stats;
418
+
419
+ walk_objects_by_path(&info);
420
+ path_walk_info_clear(&info);
421
}
422
423
static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
@@ -338,19 +426,24 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
426
struct stats_table table = {
427
.rows = STRING_LIST_INIT_DUP,
428
};
341
- struct ref_stats stats = { 0 };
429
+ struct repo_structure stats = { 0 };
430
+ struct rev_info revs;
431
struct option options[] = { 0 };
432
433
argc = parse_options(argc, argv, prefix, options, repo_usage, 0);
434
if (argc)
435
usage(_("too many arguments"));
436
348
- structure_count_references(&stats, repo);
437
+ repo_init_revisions(repo, &revs, prefix);
438
+
439
+ structure_count_references(&stats.refs, &revs, repo);
440
+ structure_count_objects(&stats.objects, &revs);
441
442
stats_table_setup_structure(&table, &stats);
443
stats_table_print_structure(&table);
444
445
stats_table_clear(&table);
446
+ release_revisions(&revs);
447
448
return 0;
449
}
t/t1901-repo-structure.sh
+17
-2
@@ -18,6 +18,13 @@ test_expect_success 'empty repository' '
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 &&
@@ -27,17 +34,18 @@ test_expect_success 'empty repository' '
34
)
35
'
36
30
-test_expect_success 'repository with references' '
37
+test_expect_success 'repository with references and objects' '
38
test_when_finished "rm -rf repo" &&
39
git init repo &&
40
(
41
cd repo &&
35
- git commit --allow-empty -m init &&
42
+ test_commit_bulk 42 &&
43
git tag -a foo -m bar &&
44
45
oid="$(git rev-parse HEAD)" &&
46
git update-ref refs/remotes/origin/foo "$oid" &&
47
48
+ # Also creates a commit, tree, and blob.
49
git notes add -m foo &&
50
51
cat >expect <<-\EOF &&
@@ -49,6 +57,13 @@ test_expect_success 'repository with references' '
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 |
67
EOF
68
69
git repo structure >out 2>err &&