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

[PATCH v5 0/6] builtin/repo: introduce structure subcommand

From
Justin Tobler <jltobler@gmail.com>
Date
Oct 15, 2025, 21:12 UTC
Message-ID
<20251015211213.361797-1-jltobler@gmail.com>
In-Reply-To
<20250927145049.723341-1-jltobler@gmail.com>
Greetings,

The structure of a repository's history can have huge impacts on the performance and health of the repository itself. Currently, Git lacks a means to surface repository metrics regarding its structure/shape via a single command. Acquiring this information requires users to be familiar with the relevant data points and the various Git commands required to surface them. To fill this gap, supplemental tools such as git-sizer(1) have been developed.

To allow users to more readily identify repository structure related information, introduce the "structure" subcommand in git-repo(1). The goal of this subcommand is to eventually provide similar functionality to git-sizer(1), but natively in Git.

In this initial version, the "structure" subcommand only surfaces counts of the various reference and object types in a repository. In a follow-up series, I would like to introduce additional data points that are present in git-sizer(1) such as largest objects, combined object sizes by type, and other general repository shape information.

Some other general features that would be nice to introduce eventually:
- A "level of concern" meter for reported stats. This could indicate to
  users which stats may be worth looking into further.
- Links to OIDs of interesting objects that correspond to certain stats.
- Options to limit which references to use when evaluating the
  repository.
Changes since V4:
- The subcommand was renamed from "stats" to "structure". This was done
  to define a more narrow scope for the types of stats that would be
  outtputted. This also also for other types of stat-related subcommands
  to be implemented in the future that may cover different aspects of
  the repository.
- Table column widths are now stored as ints to avoid the unneeded back
  and forth conversions.
- Dropped the clang-format patch as it has been upstreamed separately.
- Updated commit messages accordingly.
Changes since V3:
- Changed from using strlen() to utf8_strlen() to take into
  consideration that translatable strings may have characters that are
  more than one byte.
Changes since V2:
- Added clang-format patch to address false postive triggered in this
  series.
- Use varargs for stats_table_add() family of functions.
- Print to stdout directly instead of using strbuf.
- Add parse_option() earlier in the series.
- Use start_delayed_progress() instead of start_progress().
- Add test to validate --[no-]progress options.
- Some other small fixes.
Changes since V1:
- Translatable terms displayed in the table have formatting separated
  out.
- Squashed the `keyvalue` and `nul` output format patches into one.
- Added a progress meter to provide users with more feedback.
- Updated docs to outline to outline reported data in a bulleted list.
- Combined similar tests together to reduce repetitive setup.
- Added patch to improve ref-filter interface so we don't have to create
  a dummy patterns array.
- Many other renames and cleanups to improve patch clarity.

Thanks, -Justin

Justin Tobler (6):
  builtin/repo: rename repo_info() to cmd_repo_info()
  ref-filter: allow NULL filter pattern
  builtin/repo: introduce structure subcommand
  builtin/repo: add object counts in structure output
  builtin/repo: add keyvalue and nul format for structure stats
  builtin/repo: add progress meter for structure stats
 Documentation/git-repo.adoc |  30 +++
 builtin/repo.c              | 370 +++++++++++++++++++++++++++++++++++-
 ref-filter.c                |   4 +-
 t/meson.build               |   1 +
 t/t1901-repo-structure.sh   | 129 +++++++++++++
 5 files changed, 529 insertions(+), 5 deletions(-)
 create mode 100755 t/t1901-repo-structure.sh
Range-diff against v4:
1:  ed04168562 = 1:  ed04168562 builtin/repo: rename repo_info() to cmd_repo_info()
2:  6aa76d1323 = 2:  6aa76d1323 ref-filter: allow NULL filter pattern
3:  02a3fcc5fb < -:  ---------- clang-format: exclude control macros from SpaceBeforeParens
4:  8ec9914886 ! 3:  eda1afbe3d builtin/repo: introduce stats subcommand
    @@ Metadata
     Author: Justin Tobler <jltobler@gmail.com>
     
      ## Commit message ##
    -    builtin/repo: introduce stats subcommand
    +    builtin/repo: introduce structure subcommand
     
    -    The shape of a repository's history can have huge impacts on the
    +    The structure of a repository's history can have huge impacts on the
         performance and health of the repository itself. Currently, Git lacks a
    -    means to surface key stats/information regarding the shape of a
    -    repository via a single command. Acquiring this information requires
    -    users to be fairly knowledgeable about the structure of a Git repository
    -    and how to identify the relevant data points. To fill this gap,
    -    supplemental tools such as git-sizer(1) have been developed.
    +    means to surface repository metrics regarding its structure/shape via a
    +    single command. Acquiring this information requires users to be familiar
    +    with the relevant data points and the various Git commands required to
    +    surface them. To fill this gap, supplemental tools such as git-sizer(1)
    +    have been developed.
     
    -    To allow users to more readily identify potential issues for a
    -    repository, introduce the "stats" subcommand in git-repo(1) to output
    -    stats for the repository that may be of interest to users. The goal of
    -    this subcommand is to eventually provide similar functionality to
    -    git-sizer(1), but natively in Git.
    +    To allow users to more readily identify repository structure related
    +    information, introduce the "structure" subcommand in git-repo(1). The
    +    goal of this subcommand is to eventually provide similar functionality
    +    to git-sizer(1), but natively in Git.
     
         The initial version of this command only iterates through all references
         in the repository and tracks the count of branches, tags, remote refs,
    @@ Commit message
         to satisfy the requirements of the widest row contained.
     
         Subsequent commits will surface additional relevant data points to
    -    output.
    +    output and also provide other more machine-friendly output formats.
     
         Based-on-patch-by: Derrick Stolee <stolee@gmail.com>
         Signed-off-by: Justin Tobler <jltobler@gmail.com>
    @@ Documentation/git-repo.adoc: SYNOPSIS
      --------
      [synopsis]
      git repo info [--format=(keyvalue|nul)] [-z] [<key>...]
    -+git repo stats
    ++git repo structure
      
      DESCRIPTION
      -----------
    @@ Documentation/git-repo.adoc: supported:
      +
      `-z` is an alias for `--format=nul`.
      
    -+`stats`::
    -+	Retrieve statistics about the current repository. The following kinds
    -+	of information are reported:
    ++`structure`::
    ++	Retrieve statistics about the current repository structure. The
    ++	following kinds of information are reported:
     ++
     +* Reference counts categorized by type
     +
    @@ builtin/repo.c
      
      static const char *const repo_usage[] = {
      	"git repo info [--format=(keyvalue|nul)] [-z] [<key>...]",
    -+	"git repo stats",
    ++	"git repo structure",
      	NULL
      };
      
    @@ builtin/repo.c: static int cmd_repo_info(int argc, const char **argv, const char
     +struct stats_table {
     +	struct string_list rows;
     +
    -+	size_t name_col_width;
    -+	size_t value_col_width;
    ++	int name_col_width;
    ++	int value_col_width;
     +};
     +
     +/*
    @@ builtin/repo.c: static int cmd_repo_info(int argc, const char **argv, const char
     +	struct strbuf buf = STRBUF_INIT;
     +	struct string_list_item *item;
     +	char *formatted_name;
    -+	size_t name_width;
    ++	int name_width;
     +
     +	strbuf_vaddf(&buf, format, ap);
     +	formatted_name = strbuf_detach(&buf, NULL);
    @@ builtin/repo.c: static int cmd_repo_info(int argc, const char **argv, const char
     +	if (name_width > table->name_col_width)
     +		table->name_col_width = name_width;
     +	if (entry) {
    -+		size_t value_width = utf8_strwidth(entry->value);
    ++		int value_width = utf8_strwidth(entry->value);
     +		if (value_width > table->value_col_width)
     +			table->value_col_width = value_width;
     +	}
    @@ builtin/repo.c: static int cmd_repo_info(int argc, const char **argv, const char
     +	va_end(ap);
     +}
     +
    -+static void stats_table_setup(struct stats_table *table, struct ref_stats *refs)
    ++static void stats_table_setup_structure(struct stats_table *table,
    ++					struct ref_stats *refs)
     +{
     +	size_t ref_total;
     +
    @@ builtin/repo.c: static int cmd_repo_info(int argc, const char **argv, const char
     +	stats_table_count_addf(table, refs->others, "    * %s", _("Others"));
     +}
     +
    -+static inline size_t max_size_t(size_t a, size_t b)
    ++static void stats_table_print_structure(const struct stats_table *table)
     +{
    -+	return (a > b) ? a : b;
    -+}
    -+
    -+static void stats_table_print(const struct stats_table *table)
    -+{
    -+	const char *name_col_title = _("Repository stats");
    ++	const char *name_col_title = _("Repository structure");
     +	const char *value_col_title = _("Value");
    -+	size_t name_title_len = utf8_strwidth(name_col_title);
    -+	size_t value_title_len = utf8_strwidth(value_col_title);
    ++	int name_col_width = utf8_strwidth(name_col_title);
    ++	int value_col_width = utf8_strwidth(value_col_title);
     +	struct string_list_item *item;
    -+	int name_col_width;
    -+	int value_col_width;
     +
    -+	name_col_width = cast_size_t_to_int(
    -+		max_size_t(table->name_col_width, name_title_len));
    -+	value_col_width = cast_size_t_to_int(
    -+		max_size_t(table->value_col_width, value_title_len));
    ++	if (table->name_col_width > name_col_width)
    ++		name_col_width = table->name_col_width;
    ++	if (table->value_col_width > value_col_width)
    ++		value_col_width = table->value_col_width;
     +
     +	printf("| %-*s | %-*s |\n", name_col_width, name_col_title,
     +	       value_col_width, value_col_title);
    @@ builtin/repo.c: static int cmd_repo_info(int argc, const char **argv, const char
     +	string_list_clear(&table->rows, 1);
     +}
     +
    -+static void stats_count_references(struct ref_stats *stats, struct ref_array *refs)
    ++static void structure_count_references(struct ref_stats *stats,
    ++				       struct ref_array *refs)
     +{
     +	for (int i = 0; i < refs->nr; i++) {
     +		struct ref_array_item *ref = refs->items[i];
    @@ builtin/repo.c: static int cmd_repo_info(int argc, const char **argv, const char
     +	}
     +}
     +
    -+static int cmd_repo_stats(int argc, const char **argv, const char *prefix,
    -+			  struct repository *repo UNUSED)
    ++static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
    ++			      struct repository *repo UNUSED)
     +{
     +	struct ref_filter filter = REF_FILTER_INIT;
     +	struct stats_table table = {
    @@ builtin/repo.c: static int cmd_repo_info(int argc, const char **argv, const char
     +	if (filter_refs(&refs, &filter, FILTER_REFS_REGULAR))
     +		die(_("unable to filter refs"));
     +
    -+	stats_count_references(&stats, &refs);
    ++	structure_count_references(&stats, &refs);
     +
    -+	stats_table_setup(&table, &stats);
    -+	stats_table_print(&table);
    ++	stats_table_setup_structure(&table, &stats);
    ++	stats_table_print_structure(&table);
     +
     +	stats_table_clear(&table);
     +	ref_array_clear(&refs);
    @@ builtin/repo.c: static int cmd_repo_info(int argc, const char **argv, const char
      	parse_opt_subcommand_fn *fn = NULL;
      	struct option options[] = {
      		OPT_SUBCOMMAND("info", &fn, cmd_repo_info),
    -+		OPT_SUBCOMMAND("stats", &fn, cmd_repo_stats),
    ++		OPT_SUBCOMMAND("structure", &fn, cmd_repo_structure),
      		OPT_END()
      	};
      
    @@ t/meson.build: integration_tests = [
        't1701-racy-split-index.sh',
        't1800-hook.sh',
        't1900-repo.sh',
    -+  't1901-repo-stats.sh',
    ++  't1901-repo-structure.sh',
        't2000-conflict-when-checking-files-out.sh',
        't2002-checkout-cache-u.sh',
        't2003-checkout-cache-mkdir.sh',
     
    - ## t/t1901-repo-stats.sh (new) ##
    + ## t/t1901-repo-structure.sh (new) ##
     @@
     +#!/bin/sh
     +
    -+test_description='test git repo stats'
    ++test_description='test git repo structure'
     +
     +. ./test-lib.sh
     +
    @@ t/t1901-repo-stats.sh (new)
     +	(
     +		cd repo &&
     +		cat >expect <<-\EOF &&
    -+		| Repository stats | Value |
    -+		| ---------------- | ----- |
    -+		| * References     |       |
    -+		|   * Count        |     0 |
    -+		|     * Branches   |     0 |
    -+		|     * Tags       |     0 |
    -+		|     * Remotes    |     0 |
    -+		|     * Others     |     0 |
    ++		| Repository structure | Value |
    ++		| -------------------- | ----- |
    ++		| * References         |       |
    ++		|   * Count            |     0 |
    ++		|     * Branches       |     0 |
    ++		|     * Tags           |     0 |
    ++		|     * Remotes        |     0 |
    ++		|     * Others         |     0 |
     +		EOF
     +
    -+		git repo stats >out 2>err &&
    ++		git repo structure >out 2>err &&
     +
     +		test_cmp expect out &&
     +		test_line_count = 0 err
    @@ t/t1901-repo-stats.sh (new)
     +		git notes add -m foo &&
     +
     +		cat >expect <<-\EOF &&
    -+		| Repository stats | Value |
    -+		| ---------------- | ----- |
    -+		| * References     |       |
    -+		|   * Count        |     4 |
    -+		|     * Branches   |     1 |
    -+		|     * Tags       |     1 |
    -+		|     * Remotes    |     1 |
    -+		|     * Others     |     1 |
    ++		| Repository structure | Value |
    ++		| -------------------- | ----- |
    ++		| * References         |       |
    ++		|   * Count            |     4 |
    ++		|     * Branches       |     1 |
    ++		|     * Tags           |     1 |
    ++		|     * Remotes        |     1 |
    ++		|     * Others         |     1 |
     +		EOF
     +
    -+		git repo stats >out 2>err &&
    ++		git repo structure >out 2>err &&
     +
     +		test_cmp expect out &&
     +		test_line_count = 0 err
5:  584d35f2c7 ! 4:  503af885d3 builtin/repo: add object counts in stats output
    @@ Metadata
     Author: Justin Tobler <jltobler@gmail.com>
     
      ## Commit message ##
    -    builtin/repo: add object counts in stats output
    +    builtin/repo: add object counts in structure 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
    @@ Commit message
     
      ## Documentation/git-repo.adoc ##
     @@ Documentation/git-repo.adoc: supported:
    - 	of information are reported:
    + 	following kinds of information are reported:
      +
      * Reference counts categorized by type
     +* Reachable object counts categorized by type
    @@ builtin/repo.c: struct ref_stats {
     +	size_t blobs;
     +};
     +
    -+struct repo_stats {
    ++struct repo_structure {
     +	struct ref_stats refs;
     +	struct object_stats objects;
     +};
    @@ builtin/repo.c: static void stats_table_count_addf(struct stats_table *table, si
      	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)
    -+{
    + static void stats_table_setup_structure(struct stats_table *table,
    +-					struct ref_stats *refs)
    ++					struct repo_structure *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;
    -@@ builtin/repo.c: static void stats_table_setup(struct stats_table *table, struct ref_stats *refs)
    +@@ builtin/repo.c: static void stats_table_setup_structure(struct stats_table *table,
      	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"));
    @@ builtin/repo.c: static void stats_table_setup(struct stats_table *table, struct
     +	stats_table_count_addf(table, objects->tags, "    * %s", _("Tags"));
      }
      
    - static inline size_t max_size_t(size_t a, size_t b)
    -@@ builtin/repo.c: static void stats_count_references(struct ref_stats *stats, struct ref_array *re
    + static void stats_table_print_structure(const struct stats_table *table)
    +@@ builtin/repo.c: static void structure_count_references(struct ref_stats *stats,
      	}
      }
      
    @@ builtin/repo.c: static void stats_count_references(struct ref_stats *stats, stru
     +	return 0;
     +}
     +
    -+static void stats_count_objects(struct object_stats *stats,
    -+				struct ref_array *refs, struct rev_info *revs)
    ++static void structure_count_objects(struct object_stats *stats,
    ++				    struct ref_array *refs,
    ++				    struct rev_info *revs)
     +{
     +	struct path_walk_info info = PATH_WALK_INFO_INIT;
     +
    @@ builtin/repo.c: static void stats_count_references(struct ref_stats *stats, stru
     +	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)
    + static int cmd_repo_structure(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 repo_structure stats = { 0 };
      	struct ref_array refs = { 0 };
     +	struct rev_info revs;
      	struct option options[] = { 0 };
    @@ builtin/repo.c: static void stats_count_references(struct ref_stats *stats, stru
      	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);
    +-	structure_count_references(&stats, &refs);
    ++	structure_count_references(&stats.refs, &refs);
    ++	structure_count_objects(&stats.objects, &refs, &revs);
      
    - 	stats_table_setup(&table, &stats);
    - 	stats_table_print(&table);
    + 	stats_table_setup_structure(&table, &stats);
    + 	stats_table_print_structure(&table);
      
      	stats_table_clear(&table);
     +	release_revisions(&revs);
    @@ builtin/repo.c: static void stats_count_references(struct ref_stats *stats, stru
      
      	return 0;
     
    - ## t/t1901-repo-stats.sh ##
    -@@ t/t1901-repo-stats.sh: 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 |
    + ## t/t1901-repo-structure.sh ##
    +@@ t/t1901-repo-structure.sh: test_expect_success 'empty repository' '
    + 		|     * 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 &&
    -@@ t/t1901-repo-stats.sh: test_expect_success 'empty repository' '
    + 		git repo structure >out 2>err &&
    +@@ t/t1901-repo-structure.sh: test_expect_success 'empty repository' '
      	)
      '
      
    @@ t/t1901-repo-stats.sh: test_expect_success 'empty repository' '
      		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 |
    +@@ t/t1901-repo-structure.sh: test_expect_success 'repository with references' '
    + 		|     * 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 &&
    + 		git repo structure >out 2>err &&
6:  76975b2eab ! 5:  b336578445 builtin/repo: add keyvalue and nul format for stats
    @@ Metadata
     Author: Justin Tobler <jltobler@gmail.com>
     
      ## Commit message ##
    -    builtin/repo: add keyvalue and nul format for stats
    +    builtin/repo: add keyvalue and nul format for structure stats
     
    -    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.
    +    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
    @@ Documentation/git-repo.adoc: SYNOPSIS
      --------
      [synopsis]
      git repo info [--format=(keyvalue|nul)] [-z] [<key>...]
    --git repo stats
    -+git repo stats [--format=(table|keyvalue|nul)]
    +-git repo structure
    ++git repo structure [--format=(table|keyvalue|nul)]
      
      DESCRIPTION
      -----------
    @@ Documentation/git-repo.adoc: 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:
    +-`structure`::
    ++`structure [--format=(table|keyvalue|nul)]`::
    + 	Retrieve statistics about the current repository structure. The
    + 	following kinds of information are reported:
      +
     @@ Documentation/git-repo.adoc: supported:
      * Reachable object counts categorized by type
    @@ builtin/repo.c
      
      static const char *const repo_usage[] = {
      	"git repo info [--format=(keyvalue|nul)] [-z] [<key>...]",
    --	"git repo stats",
    -+	"git repo stats [--format=(table|keyvalue|nul)]",
    +-	"git repo structure",
    ++	"git repo structure [--format=(table|keyvalue|nul)]",
      	NULL
      };
      
    @@ builtin/repo.c: 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)
    ++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);
    @@ builtin/repo.c: static void stats_table_clear(struct stats_table *table)
     +	fflush(stdout);
     +}
     +
    - static void stats_count_references(struct ref_stats *stats, struct ref_array *refs)
    + static void structure_count_references(struct ref_stats *stats,
    + 				       struct ref_array *refs)
      {
    - 	for (int i = 0; i < refs->nr; i++) {
    -@@ builtin/repo.c: static int cmd_repo_stats(int argc, const char **argv, const char *prefix,
    +@@ builtin/repo.c: 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_stats stats = { 0 };
    + 	struct repo_structure stats = { 0 };
      	struct ref_array refs = { 0 };
      	struct rev_info revs;
     -	struct option options[] = { 0 };
    @@ builtin/repo.c: static int cmd_repo_stats(int argc, const char **argv, const cha
      
      	argc = parse_options(argc, argv, prefix, options, repo_usage, 0);
      	if (argc)
    -@@ builtin/repo.c: 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);
    +@@ builtin/repo.c: static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
    + 	structure_count_references(&stats.refs, &refs);
    + 	structure_count_objects(&stats.objects, &refs, &revs);
      
    --	stats_table_setup(&table, &stats);
    --	stats_table_print(&table);
    +-	stats_table_setup_structure(&table, &stats);
    +-	stats_table_print_structure(&table);
     +	switch (format) {
     +	case FORMAT_TABLE:
    -+		stats_table_setup(&table, &stats);
    -+		stats_table_print(&table);
    ++		stats_table_setup_structure(&table, &stats);
    ++		stats_table_print_structure(&table);
     +		break;
     +	case FORMAT_KEYVALUE:
    -+		stats_keyvalue_print(&stats, '=', '\n');
    ++		structure_keyvalue_print(&stats, '=', '\n');
     +		break;
     +	case FORMAT_NUL_TERMINATED:
    -+		stats_keyvalue_print(&stats, '\n', '\0');
    ++		structure_keyvalue_print(&stats, '\n', '\0');
     +		break;
     +	default:
     +		BUG("invalid output format");
    @@ builtin/repo.c: static int cmd_repo_stats(int argc, const char **argv, const cha
      	stats_table_clear(&table);
      	release_revisions(&revs);
     
    - ## t/t1901-repo-stats.sh ##
    -@@ t/t1901-repo-stats.sh: test_expect_success 'repository with references and objects' '
    + ## t/t1901-repo-structure.sh ##
    +@@ t/t1901-repo-structure.sh: test_expect_success 'repository with references and objects' '
      	)
      '
      
    @@ t/t1901-repo-stats.sh: test_expect_success 'repository with references and objec
     +		objects.tags.count=1
     +		EOF
     +
    -+		git repo stats --format=keyvalue >out 2>err &&
    ++		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 >expect_nul &&
    -+		git repo stats --format=nul >out 2>err &&
    ++		git repo structure --format=nul >out 2>err &&
     +
     +		test_cmp expect_nul out &&
     +		test_line_count = 0 err
7:  1105346a3c ! 6:  70c0b7e200 builtin/repo: add progress meter for stats
    @@ Metadata
     Author: Justin Tobler <jltobler@gmail.com>
     
      ## Commit message ##
    -    builtin/repo: add progress meter for stats
    +    builtin/repo: add progress meter for structure stats
     
    -    When using the stats subcommand for git-repo(1), evaluating a repository
    -    may take some time depending on its shape. Add a progress meter to
    -    provide feedback to the user about what is happening. The progress meter
    -    is enabled by default when the command is executed from a tty. It can
    -    also be explicitly enabled/disabled via the --[no-]progress option.
    +    When using the structure subcommand for git-repo(1), evaluating a
    +    repository may take some time depending on its shape. Add a progress
    +    meter to provide feedback to the user about what is happening. The
    +    progress meter is enabled by default when the command is executed from a
    +    tty. It can also be explicitly enabled/disabled via the --[no-]progress
    +    option.
     
         Signed-off-by: Justin Tobler <jltobler@gmail.com>
     
    @@ builtin/repo.c
      #include "quote.h"
      #include "ref-filter.h"
      #include "refs.h"
    -@@ builtin/repo.c: static void stats_keyvalue_print(struct repo_stats *stats, char key_delim,
    - 	fflush(stdout);
    +@@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stats,
      }
      
    --static void stats_count_references(struct ref_stats *stats, struct ref_array *refs)
    -+static void stats_count_references(struct ref_stats *stats, struct ref_array *refs,
    -+				   struct repository *repo, int show_progress)
    + static void structure_count_references(struct ref_stats *stats,
    +-				       struct ref_array *refs)
    ++				       struct ref_array *refs,
    ++				       struct repository *repo,
    ++				       int show_progress)
      {
     +	struct progress *progress = NULL;
     +
    @@ builtin/repo.c: static void stats_keyvalue_print(struct repo_stats *stats, char
      	for (int i = 0; i < refs->nr; i++) {
      		struct ref_array_item *ref = refs->items[i];
      
    -@@ builtin/repo.c: static void stats_count_references(struct ref_stats *stats, struct ref_array *re
    +@@ builtin/repo.c: static void structure_count_references(struct ref_stats *stats,
      		default:
      			BUG("unexpected reference type");
      		}
    @@ builtin/repo.c: static int count_objects(const char *path UNUSED, struct oid_arr
      	return 0;
      }
      
    - static void stats_count_objects(struct object_stats *stats,
    --				struct ref_array *refs, struct rev_info *revs)
    -+				struct ref_array *refs, struct rev_info *revs,
    -+				struct repository *repo, int show_progress)
    + static void structure_count_objects(struct object_stats *stats,
    +-				    struct ref_array *refs,
    +-				    struct rev_info *revs)
    ++				    struct ref_array *refs, struct rev_info *revs,
    ++				    struct repository *repo, int show_progress)
      {
      	struct path_walk_info info = PATH_WALK_INFO_INIT;
     +	struct count_objects_data data = {
    @@ builtin/repo.c: static int count_objects(const char *path UNUSED, struct oid_arr
      
      	for (int i = 0; i < refs->nr; i++) {
      		struct ref_array_item *ref = refs->items[i];
    -@@ builtin/repo.c: static void stats_count_objects(struct object_stats *stats,
    +@@ builtin/repo.c: static void structure_count_objects(struct object_stats *stats,
      		}
      	}
      
    @@ builtin/repo.c: static void stats_count_objects(struct object_stats *stats,
     +	stop_progress(&data.progress);
      }
      
    - static int cmd_repo_stats(int argc, const char **argv, const char *prefix,
    -@@ builtin/repo.c: static int cmd_repo_stats(int argc, const char **argv, const char *prefix,
    - 	struct repo_stats stats = { 0 };
    + static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
    +@@ builtin/repo.c: static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
    + 	struct repo_structure stats = { 0 };
      	struct ref_array refs = { 0 };
      	struct rev_info revs;
     +	int show_progress = -1;
    @@ builtin/repo.c: static int cmd_repo_stats(int argc, const char **argv, const cha
      		OPT_END()
      	};
      
    -@@ builtin/repo.c: static int cmd_repo_stats(int argc, const char **argv, const char *prefix,
    +@@ builtin/repo.c: static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
      	if (filter_refs(&refs, &filter, FILTER_REFS_REGULAR))
      		die(_("unable to filter refs"));
      
    --	stats_count_references(&stats.refs, &refs);
    --	stats_count_objects(&stats.objects, &refs, &revs);
    +-	structure_count_references(&stats.refs, &refs);
    +-	structure_count_objects(&stats.objects, &refs, &revs);
     +	if (show_progress < 0)
     +		show_progress = isatty(2);
     +
    -+	stats_count_references(&stats.refs, &refs, repo, show_progress);
    -+	stats_count_objects(&stats.objects, &refs, &revs, repo, show_progress);
    ++	structure_count_references(&stats.refs, &refs, repo, show_progress);
    ++	structure_count_objects(&stats.objects, &refs, &revs, repo, show_progress);
      
      	switch (format) {
      	case FORMAT_TABLE:
     
    - ## t/t1901-repo-stats.sh ##
    -@@ t/t1901-repo-stats.sh: test_expect_success 'keyvalue and nul format' '
    + ## t/t1901-repo-structure.sh ##
    +@@ t/t1901-repo-structure.sh: test_expect_success 'keyvalue and nul format' '
      	)
      '
      
    @@ t/t1901-repo-stats.sh: test_expect_success 'keyvalue and nul format' '
     +		cd repo &&
     +		test_commit foo &&
     +
    -+		GIT_PROGRESS_DELAY=0 git repo stats --progress >out 2>err &&
    ++		GIT_PROGRESS_DELAY=0 git repo structure --progress >out 2>err &&
     +
     +		test_file_not_empty out &&
     +		test_grep "Counting references: 100% (2/2), done." err &&
     +		test_grep "Counting objects: 3, done." err &&
     +
    -+		GIT_PROGRESS_DELAY=0 git repo stats --no-progress >out 2>err &&
    ++		GIT_PROGRESS_DELAY=0 git repo structure --no-progress >out 2>err &&
     +
     +		test_file_not_empty out &&
     +		test_line_count = 0 err
base-commit: ca2559c1d630eb4f04cdee2328aaf1c768907a9e
-- 
2.51.0.193.g4975ec3473b
Previous: Junio C HamanoNext: Justin Tobler
Message 66 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.