From 17215675b5a2c2eab54b295a7e92d953af2e8779 Mon Sep 17 00:00:00 2001 From: Justin Tobler Date: Tue, 21 Oct 2025 13:26:00 -0500 Subject: [PATCH] builtin/repo: add keyvalue and nul format for structure stats All repository structure 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 Signed-off-by: Junio C Hamano --- Documentation/git-repo.adoc | 25 +++++++++++++++-- builtin/repo.c | 55 ++++++++++++++++++++++++++++++++++--- t/t1901-repo-structure.sh | 33 ++++++++++++++++++++++ 3 files changed, 106 insertions(+), 7 deletions(-) diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc index ae62d2415f..ce43cb19c8 100644 --- a/Documentation/git-repo.adoc +++ b/Documentation/git-repo.adoc @@ -9,7 +9,7 @@ SYNOPSIS -------- [synopsis] git repo info [--format=(keyvalue|nul)] [-z] [...] -git repo structure +git repo structure [--format=(table|keyvalue|nul)] DESCRIPTION ----------- @@ -44,7 +44,7 @@ supported: + `-z` is an alias for `--format=nul`. -`structure`:: +`structure [--format=(table|keyvalue|nul)]`:: Retrieve statistics about the current repository structure. 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 --------- diff --git a/builtin/repo.c b/builtin/repo.c index f39f06ee8c..1754cc7e5d 100644 --- a/builtin/repo.c +++ b/builtin/repo.c @@ -15,13 +15,14 @@ static const char *const repo_usage[] = { "git repo info [--format=(keyvalue|nul)] [-z] [...]", - "git repo structure", + "git repo structure [--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); } @@ -330,6 +335,30 @@ static void stats_table_clear(struct stats_table *table) string_list_clear(&table->rows, 1); } +static void structure_keyvalue_print(struct repo_structure *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); +} + struct count_references_data { struct ref_stats *stats; struct rev_info *revs; @@ -426,9 +455,15 @@ static int cmd_repo_structure(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_structure stats = { 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) @@ -439,8 +474,20 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix, structure_count_references(&stats.refs, &revs, repo); structure_count_objects(&stats.objects, &revs); - stats_table_setup_structure(&table, &stats); - stats_table_print_structure(&table); + switch (format) { + case FORMAT_TABLE: + stats_table_setup_structure(&table, &stats); + stats_table_print_structure(&table); + break; + case FORMAT_KEYVALUE: + structure_keyvalue_print(&stats, '=', '\n'); + break; + case FORMAT_NUL_TERMINATED: + structure_keyvalue_print(&stats, '\n', '\0'); + break; + default: + BUG("invalid output format"); + } stats_table_clear(&table); release_revisions(&revs); diff --git a/t/t1901-repo-structure.sh b/t/t1901-repo-structure.sh index c32cf4e239..14bd8aede5 100755 --- a/t/t1901-repo-structure.sh +++ b/t/t1901-repo-structure.sh @@ -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 structure --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_nul && + git repo structure --format=nul >out 2>err && + + test_cmp expect_nul out && + test_line_count = 0 err + ) +' + test_done