git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [PATCH v2 5/6] builtin/repo: add keyvalue and nul format for stats

From
Patrick Steinhardt <ps@pks.im>
Date
Sep 25, 2025, 05:39 UTC
Message-ID
<aNTVdy4hhLDlMpVT@pks.im>
In-Reply-To
<20250924212426.2930029-6-jltobler@gmail.com>
On Wed, Sep 24, 2025 at 04:24:25PM -0500, Justin Tobler wrote:
Show 16 quoted lines
> diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc
> index 0b8d74ed3e..db21b75522 100644
> --- a/Documentation/git-repo.adoc
> +++ b/Documentation/git-repo.adoc
> @@ -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 and is used by
> +	default. This format may change and is not intended for machine
> +	parsing.
Let's mention that this is the default format.
Show 5 quoted lines
> +`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]).

In the current state there is never any quoting, so this statement here is a bit misleading. Should we maybe drop that part?

Show 6 quoted lines
> +`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.
Likewise.
Show 34 quoted lines
> diff --git a/builtin/repo.c b/builtin/repo.c
> index 8f130bca66..fe7d43f78e 100644
> --- a/builtin/repo.c
> +++ b/builtin/repo.c
> @@ -312,6 +317,33 @@ 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)
> +{
> +	struct strbuf buf = STRBUF_INIT;
> +
> +	strbuf_addf(&buf, "references.branches.count%c%" PRIuMAX "%c",
> +		    key_delim, (uintmax_t)stats->refs.branches, value_delim);
> +	strbuf_addf(&buf, "references.tags.count%c%" PRIuMAX "%c",
> +		    key_delim, (uintmax_t)stats->refs.tags, value_delim);
> +	strbuf_addf(&buf, "references.remotes.count%c%" PRIuMAX "%c",
> +		    key_delim, (uintmax_t)stats->refs.remotes, value_delim);
> +	strbuf_addf(&buf, "references.others.count%c%" PRIuMAX "%c",
> +		    key_delim, (uintmax_t)stats->refs.others, value_delim);
> +
> +	strbuf_addf(&buf, "objects.commits.count%c%" PRIuMAX "%c",
> +		    key_delim, (uintmax_t)stats->objects.commits, value_delim);
> +	strbuf_addf(&buf, "objects.trees.count%c%" PRIuMAX "%c",
> +		    key_delim, (uintmax_t)stats->objects.trees, value_delim);
> +	strbuf_addf(&buf, "objects.blobs.count%c%" PRIuMAX "%c",
> +		    key_delim, (uintmax_t)stats->objects.blobs, value_delim);
> +	strbuf_addf(&buf, "objects.tags.count%c%" PRIuMAX "%c",
> +		    key_delim, (uintmax_t)stats->objects.tags, value_delim);
> +
> +	fwrite(buf.buf, sizeof(char), buf.len, stdout);
> +	strbuf_release(&buf);
> +}

Same question here regarding the buffering. Can't we print to stdout directly, or is there a reason not to?

Show 25 quoted lines
> @@ -389,17 +421,25 @@ static void stats_count_objects(struct object_stats *stats,
>  	path_walk_info_clear(&info);
>  }
>  
> -static int cmd_repo_stats(int argc UNUSED, const char **argv UNUSED,
> -			  const char *prefix, struct repository *repo)
> +static int cmd_repo_stats(int argc, const char **argv, const char *prefix,
> +			  struct repository *repo)
>  {
>  	struct ref_filter filter = REF_FILTER_INIT;
>  	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[] = {
> +		OPT_CALLBACK_F(0, "format", &format, N_("format"),
> +			       N_("output format"),
> +			       PARSE_OPT_NONEG, parse_format_cb),
> +		OPT_END()
> +	};
>  
> +	parse_options(argc, argv, prefix, options, repo_usage, 0);

I think it would be sensible to introduce this call to `parse_options()` right in the first commit that wires up the new subcommand. If we don't do that we otherwise accept arbitrary arguments without raising any error, and neither do we know to output help.

So we should move the addition to a previous commit and probably do the following:

        argc = parse_options(...);
        if (argc)
            usagef("too many arguments");
Show 17 quoted lines
> @@ -407,8 +447,20 @@ static int cmd_repo_stats(int argc UNUSED, const char **argv UNUSED,
>  	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;

This reads much nicer now. The newline as key-value delimiter is a curious choice, but you simply do what we already do in `git repo info`.

Show 34 quoted lines
> diff --git a/t/t1901-repo-stats.sh b/t/t1901-repo-stats.sh
> index 315b9e1767..d2c1b6e307 100755
> --- a/t/t1901-repo-stats.sh
> +++ b/t/t1901-repo-stats.sh
> @@ -73,4 +73,37 @@ test_expect_success 'repository with references and objects' '
>  	)
>  '
>  
> +test_expect_success 'repository stats with 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" <expect | tr "=" "\n" >expect_null &&
You can do this without the pipe:
    tr "\n=" "\0\n" <expect >expect_nul

Also, let's call the file `expect_nul` (with a single 'l') to match the format.

Patrick
Previous: Justin ToblerNext: Justin Tobler
Message 32 of 92 in “builtin/repo: introduce stats subcommand”
  1. 0/4 builtin/repo: introduce stats subcommandJustin Tobler, Sep 23, 2025
  2. 1/4 builtin/repo: introduce stats subcommandJustin Tobler, Sep 23, 2025
  3. Patrick SteinhardtSep 23, 2025
  4. Justin ToblerSep 23, 2025
  5. Patrick SteinhardtSep 23, 2025
  6. Karthik NayakSep 23, 2025
  7. Justin ToblerSep 23, 2025
  8. 2/4 builtin/repo: add object counts in stats outputJustin Tobler, Sep 23, 2025
  9. Patrick SteinhardtSep 23, 2025
  10. Justin ToblerSep 23, 2025
  11. Karthik NayakSep 23, 2025
  12. Justin ToblerSep 23, 2025
  13. 3/4 builtin/repo: add keyvalue format for statsJustin Tobler, Sep 23, 2025
  14. Patrick SteinhardtSep 23, 2025
  15. Justin ToblerSep 23, 2025
  16. Karthik NayakSep 23, 2025
  17. Justin ToblerSep 23, 2025
  18. 4/4 builtin/repo: add nul format for statsJustin Tobler, Sep 23, 2025
  19. Patrick SteinhardtSep 23, 2025
  20. Justin ToblerSep 23, 2025
  21. Patrick SteinhardtSep 24, 2025
  22. Karthik NayakSep 23, 2025
  23. Justin ToblerSep 23, 2025
  24. 0/6 builtin/repo: introduce stats subcommandJustin Tobler, Sep 24, 2025
  25. 1/6 builtin/repo: rename repo_info() to cmd_repo_info()Justin Tobler, Sep 24, 2025
  26. 2/6 ref-filter: allow NULL filter patternJustin Tobler, Sep 24, 2025
  27. 3/6 builtin/repo: introduce stats subcommandJustin Tobler, Sep 24, 2025
  28. Patrick SteinhardtSep 25, 2025
  29. Justin ToblerSep 25, 2025
  30. 4/6 builtin/repo: add object counts in stats outputJustin Tobler, Sep 24, 2025
  31. 5/6 builtin/repo: add keyvalue and nul format for statsJustin Tobler, Sep 24, 2025
  32. Patrick SteinhardtSep 25, 2025
  33. Justin ToblerSep 25, 2025
  34. Patrick SteinhardtSep 25, 2025
  35. 6/6 builtin/repo: add progress meter for statsJustin Tobler, Sep 24, 2025
  36. Patrick SteinhardtSep 25, 2025
  37. Justin ToblerSep 25, 2025
  38. 0/7 builtin/repo: introduce stats subcommandJustin Tobler, Sep 25, 2025
  39. 1/7 builtin/repo: rename repo_info() to cmd_repo_info()Justin Tobler, Sep 25, 2025
  40. 2/7 ref-filter: allow NULL filter patternJustin Tobler, Sep 25, 2025
  41. 3/7 clang-format: exclude control macros from SpaceBeforeParensJustin Tobler, Sep 25, 2025
  42. 4/7 builtin/repo: introduce stats subcommandJustin Tobler, Sep 25, 2025
  43. Eric SunshineSep 25, 2025
  44. Justin ToblerSep 26, 2025
  45. 5/7 builtin/repo: add object counts in stats outputJustin Tobler, Sep 25, 2025
  46. 6/7 builtin/repo: add keyvalue and nul format for statsJustin Tobler, Sep 25, 2025
  47. 7/7 builtin/repo: add progress meter for statsJustin Tobler, Sep 25, 2025
  48. 0/7 builtin/repo: introduce stats subcommandJustin Tobler, Sep 27, 2025
  49. 1/7 builtin/repo: rename repo_info() to cmd_repo_info()Justin Tobler, Sep 27, 2025
  50. 2/7 ref-filter: allow NULL filter patternJustin Tobler, Sep 27, 2025
  51. 3/7 clang-format: exclude control macros from SpaceBeforeParensJustin Tobler, Sep 27, 2025
  52. Junio C HamanoSep 27, 2025
  53. Justin ToblerSep 27, 2025
  54. Junio C HamanoSep 27, 2025
  55. 4/7 builtin/repo: introduce stats subcommandJustin Tobler, Sep 27, 2025
  56. Junio C HamanoSep 27, 2025
  57. Justin ToblerOct 9, 2025
  58. Justin ToblerOct 10, 2025
  59. Patrick SteinhardtOct 10, 2025
  60. Justin ToblerOct 10, 2025
  61. Patrick SteinhardtOct 13, 2025
  62. 6/7 builtin/repo: add keyvalue and nul format for statsJustin Tobler, Sep 27, 2025
  63. 5/7 builtin/repo: add object counts in stats outputJustin Tobler, Sep 27, 2025
  64. 7/7 builtin/repo: add progress meter for statsJustin Tobler, Sep 27, 2025
  65. Junio C HamanoSep 27, 2025
  66. 0/6 builtin/repo: introduce structure subcommandJustin Tobler, Oct 15, 2025
  67. 1/6 builtin/repo: rename repo_info() to cmd_repo_info()Justin Tobler, Oct 15, 2025
  68. 2/6 ref-filter: allow NULL filter patternJustin Tobler, Oct 15, 2025
  69. 3/6 builtin/repo: introduce structure subcommandJustin Tobler, Oct 15, 2025
  70. Patrick SteinhardtOct 16, 2025
  71. Justin ToblerOct 21, 2025
  72. 4/6 builtin/repo: add object counts in structure outputJustin Tobler, Oct 15, 2025
  73. 5/6 builtin/repo: add keyvalue and nul format for structure statsJustin Tobler, Oct 15, 2025
  74. 6/6 builtin/repo: add progress meter for structure statsJustin Tobler, Oct 15, 2025
  75. 0/7 builtin/repo: introduce structure subcommandJustin Tobler, Oct 21, 2025
  76. 1/7 builtin/repo: rename repo_info() to cmd_repo_info()Justin Tobler, Oct 21, 2025
  77. 2/7 ref-filter: allow NULL filter patternJustin Tobler, Oct 21, 2025
  78. 3/7 ref-filter: export ref_kind_from_refname()Justin Tobler, Oct 21, 2025
  79. 4/7 builtin/repo: introduce structure subcommandJustin Tobler, Oct 21, 2025
  80. Patrick SteinhardtOct 22, 2025
  81. Justin ToblerOct 22, 2025
  82. Lucas Seiki OshiroOct 22, 2025
  83. Justin ToblerOct 22, 2025
  84. 5/7 builtin/repo: add object counts in structure outputJustin Tobler, Oct 21, 2025
  85. 6/7 builtin/repo: add keyvalue and nul format for structure statsJustin Tobler, Oct 21, 2025
  86. Lucas Seiki OshiroOct 22, 2025
  87. Justin ToblerOct 23, 2025
  88. 7/7 builtin/repo: add progress meter for structure statsJustin Tobler, Oct 21, 2025
  89. Lucas Seiki OshiroOct 22, 2025
  90. Justin ToblerOct 23, 2025
  91. Junio C HamanoOct 23, 2025
  92. Patrick SteinhardtOct 24, 2025

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.