]> git.ipfire.org Git - thirdparty/git.git/commitdiff
builtin/repo: add object counts in stats output
authorJustin Tobler <jltobler@gmail.com>
Sat, 27 Sep 2025 14:50:47 +0000 (09:50 -0500)
committerJunio C Hamano <gitster@pobox.com>
Sun, 28 Sep 2025 15:39:11 +0000 (08:39 -0700)
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>
Documentation/git-repo.adoc
builtin/repo.c
t/t1901-repo-stats.sh

index a009bf8cf1bf2f5e5c96c3bb8635b01bd6ed70b2..0b8d74ed3eb53691e98929bc2260c4721a396e57 100644 (file)
@@ -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.
index 889e344f158d3decf082f6df5a932a1b1e3ad900..3eefbeddba27decf595c1a4355dbcc9a91e0d2f3 100644 (file)
@@ -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;
index 535ac511dda428206c03d761d5ddb49d83df19b1..315b9e1767bdbb1887084303555041e8b1858c20 100755 (executable)
@@ -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 &&