From: Justin Tobler Date: Sat, 27 Sep 2025 14:50:47 +0000 (-0500) Subject: builtin/repo: add object counts in stats output X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=8589bad4130007151ff50430807a80b646a69bdc;p=thirdparty%2Fgit.git builtin/repo: add object counts in stats 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 Signed-off-by: Junio C Hamano --- diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc index a009bf8cf1..0b8d74ed3e 100644 --- a/Documentation/git-repo.adoc +++ b/Documentation/git-repo.adoc @@ -49,6 +49,7 @@ supported: of information are reported: + * Reference counts categorized by type +* Reachable object counts categorized by type + The table output format may change and is not intended for machine parsing. diff --git a/builtin/repo.c b/builtin/repo.c index 889e344f15..3eefbeddba 100644 --- a/builtin/repo.c +++ b/builtin/repo.c @@ -3,9 +3,11 @@ #include "builtin.h" #include "environment.h" #include "parse-options.h" +#include "path-walk.h" #include "quote.h" #include "ref-filter.h" #include "refs.h" +#include "revision.h" #include "strbuf.h" #include "string-list.h" #include "shallow.h" @@ -167,6 +169,18 @@ struct ref_stats { size_t others; }; +struct object_stats { + size_t tags; + size_t commits; + size_t trees; + size_t blobs; +}; + +struct repo_stats { + struct ref_stats refs; + struct object_stats objects; +}; + struct stats_table { struct string_list rows; @@ -229,8 +243,16 @@ static void stats_table_count_addf(struct stats_table *table, size_t value, va_end(ap); } -static void stats_table_setup(struct stats_table *table, struct ref_stats *refs) +static inline size_t get_total_object_count(struct object_stats *stats) { + return stats->tags + stats->commits + stats->trees + stats->blobs; +} + +static void stats_table_setup(struct stats_table *table, struct repo_stats *stats) +{ + struct object_stats *objects = &stats->objects; + struct ref_stats *refs = &stats->refs; + size_t object_total; size_t ref_total; ref_total = refs->branches + refs->remotes + refs->tags + refs->others; @@ -240,6 +262,15 @@ static void stats_table_setup(struct stats_table *table, struct ref_stats *refs) stats_table_count_addf(table, refs->tags, " * %s", _("Tags")); stats_table_count_addf(table, refs->remotes, " * %s", _("Remotes")); stats_table_count_addf(table, refs->others, " * %s", _("Others")); + + object_total = get_total_object_count(objects); + stats_table_addf(table, ""); + stats_table_addf(table, "* %s", _("Reachable objects")); + stats_table_count_addf(table, object_total, " * %s", _("Count")); + stats_table_count_addf(table, objects->commits, " * %s", _("Commits")); + stats_table_count_addf(table, objects->trees, " * %s", _("Trees")); + stats_table_count_addf(table, objects->blobs, " * %s", _("Blobs")); + stats_table_count_addf(table, objects->tags, " * %s", _("Tags")); } static inline size_t max_size_t(size_t a, size_t b) @@ -324,30 +355,87 @@ static void stats_count_references(struct ref_stats *stats, struct ref_array *re } } +static int count_objects(const char *path UNUSED, struct oid_array *oids, + enum object_type type, void *cb_data) +{ + struct object_stats *stats = cb_data; + + switch (type) { + case OBJ_TAG: + stats->tags += oids->nr; + break; + case OBJ_COMMIT: + stats->commits += oids->nr; + break; + case OBJ_TREE: + stats->trees += oids->nr; + break; + case OBJ_BLOB: + stats->blobs += oids->nr; + break; + default: + BUG("invalid object type"); + } + + return 0; +} + +static void stats_count_objects(struct object_stats *stats, + struct ref_array *refs, struct rev_info *revs) +{ + struct path_walk_info info = PATH_WALK_INFO_INIT; + + info.revs = revs; + info.path_fn = count_objects; + info.path_fn_data = stats; + + for (int i = 0; i < refs->nr; i++) { + struct ref_array_item *ref = refs->items[i]; + + switch (ref->kind) { + case FILTER_REFS_BRANCHES: + case FILTER_REFS_TAGS: + case FILTER_REFS_REMOTES: + case FILTER_REFS_OTHERS: + add_pending_oid(revs, NULL, &ref->objectname, 0); + break; + default: + BUG("unexpected reference type"); + } + } + + walk_objects_by_path(&info); + path_walk_info_clear(&info); +} + static int cmd_repo_stats(int argc, const char **argv, const char *prefix, - struct repository *repo UNUSED) + struct repository *repo) { struct ref_filter filter = REF_FILTER_INIT; struct stats_table table = { .rows = STRING_LIST_INIT_DUP, }; - struct ref_stats stats = { 0 }; + struct repo_stats stats = { 0 }; struct ref_array refs = { 0 }; + struct rev_info revs; struct option options[] = { 0 }; argc = parse_options(argc, argv, prefix, options, repo_usage, 0); if (argc) usage(_("too many arguments")); + repo_init_revisions(repo, &revs, prefix); if (filter_refs(&refs, &filter, FILTER_REFS_REGULAR)) die(_("unable to filter refs")); - stats_count_references(&stats, &refs); + stats_count_references(&stats.refs, &refs); + stats_count_objects(&stats.objects, &refs, &revs); stats_table_setup(&table, &stats); stats_table_print(&table); stats_table_clear(&table); + release_revisions(&revs); ref_array_clear(&refs); return 0; diff --git a/t/t1901-repo-stats.sh b/t/t1901-repo-stats.sh index 535ac511dd..315b9e1767 100755 --- a/t/t1901-repo-stats.sh +++ b/t/t1901-repo-stats.sh @@ -10,14 +10,21 @@ test_expect_success 'empty repository' ' ( cd repo && cat >expect <<-\EOF && - | Repository stats | Value | - | ---------------- | ----- | - | * References | | - | * Count | 0 | - | * Branches | 0 | - | * Tags | 0 | - | * Remotes | 0 | - | * Others | 0 | + | Repository stats | Value | + | ------------------- | ----- | + | * References | | + | * Count | 0 | + | * Branches | 0 | + | * Tags | 0 | + | * Remotes | 0 | + | * Others | 0 | + | | | + | * Reachable objects | | + | * Count | 0 | + | * Commits | 0 | + | * Trees | 0 | + | * Blobs | 0 | + | * Tags | 0 | EOF git repo stats >out 2>err && @@ -27,28 +34,36 @@ test_expect_success 'empty repository' ' ) ' -test_expect_success 'repository with references' ' +test_expect_success 'repository with references and objects' ' test_when_finished "rm -rf repo" && git init repo && ( cd repo && - git commit --allow-empty -m init && + test_commit_bulk 42 && git tag -a foo -m bar && oid="$(git rev-parse HEAD)" && git update-ref refs/remotes/origin/foo "$oid" && + # Also creates a commit, tree, and blob. git notes add -m foo && cat >expect <<-\EOF && - | Repository stats | Value | - | ---------------- | ----- | - | * References | | - | * Count | 4 | - | * Branches | 1 | - | * Tags | 1 | - | * Remotes | 1 | - | * Others | 1 | + | Repository stats | Value | + | ------------------- | ----- | + | * References | | + | * Count | 4 | + | * Branches | 1 | + | * Tags | 1 | + | * Remotes | 1 | + | * Others | 1 | + | | | + | * Reachable objects | | + | * Count | 130 | + | * Commits | 43 | + | * Trees | 43 | + | * Blobs | 43 | + | * Tags | 1 | EOF git repo stats >out 2>err &&