]> git.ipfire.org Git - thirdparty/git.git/commitdiff
builtin/repo: add keyvalue and nul format for stats
authorJustin Tobler <jltobler@gmail.com>
Sat, 27 Sep 2025 14:50:48 +0000 (09:50 -0500)
committerJunio C Hamano <gitster@pobox.com>
Sun, 28 Sep 2025 15:39:11 +0000 (08:39 -0700)
All repository stats are outputted in a human-friendly table form. This
format is not suitable for machine parsing. Add a --format option that
supports three output modes: `table`, `keyvalue`, and `nul`. The `table`
mode is the default format and prints the same table output as before.

With the `keyvalue` mode, each line of output contains a key-value pair
of a repository stat. The '=' character is used to delimit between keys
and values. The `nul` mode is similar to `keyvalue`, but key-values are
delimited by a NUL character instead of a newline. Also, instead of a
'=' character to delimit between keys and values, a newline character is
used. This allows stat values to support special characters without
having to cquote them. These two new modes provides output that is more
machine-friendly.

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 0b8d74ed3eb53691e98929bc2260c4721a396e57..3fbce0b88cd46d5e3923cabe984ba4276c4765a6 100644 (file)
@@ -9,7 +9,7 @@ SYNOPSIS
 --------
 [synopsis]
 git repo info [--format=(keyvalue|nul)] [-z] [<key>...]
-git repo stats
+git repo stats [--format=(table|keyvalue|nul)]
 
 DESCRIPTION
 -----------
@@ -44,7 +44,7 @@ supported:
 +
 `-z` is an alias for `--format=nul`.
 
-`stats`::
+`stats [--format=(table|keyvalue|nul)]`::
        Retrieve statistics about the current repository. The following kinds
        of information are reported:
 +
@@ -52,7 +52,26 @@ supported:
 * Reachable object counts categorized by type
 
 +
-The table output format may change and is not intended for machine parsing.
+The output format can be chosen through the flag `--format`. Three formats are
+supported:
++
+`table`:::
+       Outputs repository stats in a human-friendly table. This format may
+       change and is not intended for machine parsing. This is the default
+       format.
+
+`keyvalue`:::
+       Each line of output contains a key-value pair for a repository stat.
+       The '=' character is used to delimit between the key and the value.
+       Values containing "unusual" characters are quoted as explained for the
+       configuration variable `core.quotePath` (see linkgit:git-config[1]).
+
+`nul`:::
+       Similar to `keyvalue`, but uses a NUL character to delimit between
+       key-value pairs instead of a newline. Also uses a newline character as
+       the delimiter between the key and value instead of '='. Unlike the
+       `keyvalue` format, values containing "unusual" characters are never
+       quoted.
 
 INFO KEYS
 ---------
index 3eefbeddba27decf595c1a4355dbcc9a91e0d2f3..6f41c9ada2b2faaf8504506dbbf2318d02a32c1c 100644 (file)
 
 static const char *const repo_usage[] = {
        "git repo info [--format=(keyvalue|nul)] [-z] [<key>...]",
-       "git repo stats",
+       "git repo stats [--format=(table|keyvalue|nul)]",
        NULL
 };
 
 typedef int get_value_fn(struct repository *repo, struct strbuf *buf);
 
 enum output_format {
+       FORMAT_TABLE,
        FORMAT_KEYVALUE,
        FORMAT_NUL_TERMINATED,
 };
@@ -136,6 +137,8 @@ static int parse_format_cb(const struct option *opt,
                *format = FORMAT_NUL_TERMINATED;
        else if (!strcmp(arg, "keyvalue"))
                *format = FORMAT_KEYVALUE;
+       else if (!strcmp(arg, "table"))
+               *format = FORMAT_TABLE;
        else
                die(_("invalid format '%s'"), arg);
 
@@ -158,6 +161,8 @@ static int cmd_repo_info(int argc, const char **argv, const char *prefix,
        };
 
        argc = parse_options(argc, argv, prefix, options, repo_usage, 0);
+       if (format != FORMAT_KEYVALUE && format != FORMAT_NUL_TERMINATED)
+               die(_("unsupported output format"));
 
        return print_fields(argc, argv, repo, format);
 }
@@ -331,6 +336,30 @@ static void stats_table_clear(struct stats_table *table)
        string_list_clear(&table->rows, 1);
 }
 
+static void stats_keyvalue_print(struct repo_stats *stats, char key_delim,
+                                char value_delim)
+{
+       printf("references.branches.count%c%" PRIuMAX "%c", key_delim,
+              (uintmax_t)stats->refs.branches, value_delim);
+       printf("references.tags.count%c%" PRIuMAX "%c", key_delim,
+              (uintmax_t)stats->refs.tags, value_delim);
+       printf("references.remotes.count%c%" PRIuMAX "%c", key_delim,
+              (uintmax_t)stats->refs.remotes, value_delim);
+       printf("references.others.count%c%" PRIuMAX "%c", key_delim,
+              (uintmax_t)stats->refs.others, value_delim);
+
+       printf("objects.commits.count%c%" PRIuMAX "%c", key_delim,
+              (uintmax_t)stats->objects.commits, value_delim);
+       printf("objects.trees.count%c%" PRIuMAX "%c", key_delim,
+              (uintmax_t)stats->objects.trees, value_delim);
+       printf("objects.blobs.count%c%" PRIuMAX "%c", key_delim,
+              (uintmax_t)stats->objects.blobs, value_delim);
+       printf("objects.tags.count%c%" PRIuMAX "%c", key_delim,
+              (uintmax_t)stats->objects.tags, value_delim);
+
+       fflush(stdout);
+}
+
 static void stats_count_references(struct ref_stats *stats, struct ref_array *refs)
 {
        for (int i = 0; i < refs->nr; i++) {
@@ -415,10 +444,16 @@ static int cmd_repo_stats(int argc, const char **argv, const char *prefix,
        struct stats_table table = {
                .rows = STRING_LIST_INIT_DUP,
        };
+       enum output_format format = FORMAT_TABLE;
        struct repo_stats stats = { 0 };
        struct ref_array refs = { 0 };
        struct rev_info revs;
-       struct option options[] = { 0 };
+       struct option options[] = {
+               OPT_CALLBACK_F(0, "format", &format, N_("format"),
+                              N_("output format"),
+                              PARSE_OPT_NONEG, parse_format_cb),
+               OPT_END()
+       };
 
        argc = parse_options(argc, argv, prefix, options, repo_usage, 0);
        if (argc)
@@ -431,8 +466,20 @@ static int cmd_repo_stats(int argc, const char **argv, const char *prefix,
        stats_count_references(&stats.refs, &refs);
        stats_count_objects(&stats.objects, &refs, &revs);
 
-       stats_table_setup(&table, &stats);
-       stats_table_print(&table);
+       switch (format) {
+       case FORMAT_TABLE:
+               stats_table_setup(&table, &stats);
+               stats_table_print(&table);
+               break;
+       case FORMAT_KEYVALUE:
+               stats_keyvalue_print(&stats, '=', '\n');
+               break;
+       case FORMAT_NUL_TERMINATED:
+               stats_keyvalue_print(&stats, '\n', '\0');
+               break;
+       default:
+               BUG("invalid output format");
+       }
 
        stats_table_clear(&table);
        release_revisions(&revs);
index 315b9e1767bdbb1887084303555041e8b1858c20..2409edae4f37e3eb07f5ea54c602ba374d7fadf7 100755 (executable)
@@ -73,4 +73,37 @@ test_expect_success 'repository with references and objects' '
        )
 '
 
+test_expect_success 'keyvalue and nul format' '
+       test_when_finished "rm -rf repo" &&
+       git init repo &&
+       (
+               cd repo &&
+               test_commit_bulk 42 &&
+               git tag -a foo -m bar &&
+
+               cat >expect <<-\EOF &&
+               references.branches.count=1
+               references.tags.count=1
+               references.remotes.count=0
+               references.others.count=0
+               objects.commits.count=42
+               objects.trees.count=42
+               objects.blobs.count=42
+               objects.tags.count=1
+               EOF
+
+               git repo stats --format=keyvalue >out 2>err &&
+
+               test_cmp expect out &&
+               test_line_count = 0 err &&
+
+               # Replace key and value delimiters for nul format.
+               tr "\n=" "\0\n" <expect >expect_nul &&
+               git repo stats --format=nul >out 2>err &&
+
+               test_cmp expect_nul out &&
+               test_line_count = 0 err
+       )
+'
+
 test_done