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

[PATCH v5 0/2] repo: add degree-of-concern reporting to structure

From
MCMark C. Chu-Carroll <markchucarroll@fastmail.com>
Date
Nov 2, 2026, 16:32 UTC
Message-ID
<20261102-b4-repo-structure-concern-v5-0-2d8f37b70b75@fastmail.com>
In-Reply-To
<20261021-b4-repo-structure-concern-v1-0-e3010552292c@fastmail.com>

"git repo structure" computes a set of statistics that are useful for diagnosing performance issues in a git repository. But for a typical user who hasn't encountered any issues yet, it can be difficult to interpret the data - when should a value be considered concerning?

This patchset introduces a feature to "git repo structure" that reports on how concerning certain measures of storage structure are for a repository. In table mode, concern is displayed as a column containing a number of stars indicating the degree of concern; in line or nul mode, concerns are displayed as additional lines with a numeric value. This provides a functionality equivalent to the concern column provided by "git-sizer", although the implementation is different.

To select defaults for the thresholds where a value becomes concern, a (hopefully) representative selection of repositories were analyzed. Based on empirical exploration of the data, the measurement that seemed closest to accurately assessing when a repository's structure becomes concerning was based on the base 2 logarithm of the value. Across all of the sample repositories, for all of the measurements, the point at which things become concerning was when a base 2 logarithm of a statistic's value was more than 1 standard deviation from the mean of the log_2 values of that stastistic in the sample set. Based on this, the concern threshold was set for each of the measures.

While this seems like a reasonable approach for setting default thresholds, it's obviously still very ad-hoc. It's likely that over time, the default values will evolve; and in any case, some projects will want to set their own thresholds. To allow that, this includes support for configuring the thresholds on a per-repository basis.

Overview of the change:
         
* The first commit adds the command-line option "--concern", and creates the basic implementation of concern reporting.
* The next commit adds configurability for the concern thresholds.
Signed-off-by: Mark C. Chu-Carroll <markchucarroll@fastmail.com>
---
Changes in v5:
- whitespace fixes
- change configuration values to make them more intuitive.
- fix cut-and-paste error
- fix location-of-deallocation error of strbuf in table generation
- removed invalid concern for tag-refs.
- fix integer typing in concern computation and table generation.
- Link to v4: https://patch.msgid.link/20261029-b4-repo-structure-concern-v4-0-d813537ec617@fastmail.com
Changes in v4:
- Reworked the history; the previous round had a history-rewrite error.
- Removed the duplication of configuration keys for the concern thresholds.
- Changed the prefix of the config keys.
- readability/quality of life naming changes.
- Link to v3: https://patch.msgid.link/20261027-b4-repo-structure-concern-v3-0-98d7a5704170@fastmail.com
Changes in v3:
- Merge the documentation commit backwards into the other two commits.
- Link to v2: https://patch.msgid.link/20261026-b4-repo-structure-concern-v2-0-e0df34228dc8@fastmail.com
Changes in v2:
- Fix memory leak in concern string generation.
- Fix CI/CD test issue involving zlib versions.
- Updating the naming of the config value prefix.
- Link to v1: https://patch.msgid.link/20261021-b4-repo-structure-concern-v1-0-e3010552292c@fastmail.com
To: git@vger.kernel.org
---
Mark C. Chu-Carroll (2):
      repo: add level of concern reporting
      repo: make concern thresholds configurable
 Documentation/config/repo.adoc |  63 ++++++
 Documentation/git-repo.adoc    |  27 ++-
 builtin/repo.c                 | 479 +++++++++++++++++++++++++++++++++++++----
 t/t1901-repo-structure.sh      | 138 +++++++++++-
 4 files changed, 668 insertions(+), 39 deletions(-)
Range-diff versus v4:
1:  66d6fc14d4 ! 1:  c5100f5fa3 repo: add level of concern reporting
    @@ Documentation/git-repo.adoc: supported:
      ---------
     
      ## builtin/repo.c ##
    +@@
    + #define USE_THE_REPOSITORY_VARIABLE
    + 
    ++#include "git-compat-util.h"
    + #include "builtin.h"
    + #include "commit.h"
    ++#include "config.h"
    + #include "environment.h"
    + #include "hash.h"
    + #include "hex.h"
     @@
      #include "tree.h"
      #include "tree-walk.h"
    @@ builtin/repo.c
      #define REPO_STRUCTURE_USAGE \
     -	"git repo structure [--format=(table|lines|nul) | -z]"
     +	"git repo structure [--format=(table|lines|nul) | -z] [--[no-]concern]"
    ++
    ++#define REPO_STRUCTURE_CONFIG_PREFIX "repo.concern."
      
      static const char *const repo_usage[] = {
      	REPO_INFO_USAGE,
    @@ builtin/repo.c: struct stats_table {
      	int name_col_width;
      	int value_col_width;
      	int unit_col_width;
    -+	int concern_col_width;
    - };
    - 
    ++	size_t max_concern_value;
    ++};
    ++
     +/*
     + * For computing level of concern about a stat, we need to have some
    -+ * baseline values. For each of these, we'define a minimum level
    ++ * baseline values. For each of these, we define a minimum level
     + * of concern, which will appear as 1 star. Then we scale exponentially:
     + * each time that value doubles, we add another star.
     + *
    @@ builtin/repo.c: struct stats_table {
     +struct concern_stat_threshold {
     +	const char *name;
     +	size_t threshold;
    -+};
    -+
    + };
    + 
     +#define CONCERN_STAT_COMMIT_COUNT "commit-count"
     +#define CONCERN_STAT_TREE_COUNT "tree-count"
     +#define CONCERN_STAT_BLOB_COUNT "blob-count"
    @@ builtin/repo.c: struct stats_table {
     +#define CONCERN_STAT_BLOB_INFLATED_SIZE "blob-inflated-size"
     +#define CONCERN_STAT_TAG_INFLATED_SIZE "tag-inflated-size"
     +
    -+struct concern_stat_threshold repo_structure_concern_thresholds[] = {
    ++static struct concern_stat_threshold repo_structure_concern_thresholds[] = {
     +	{ .name = CONCERN_STAT_COMMIT_COUNT, .threshold = 19 },
     +	{ .name = CONCERN_STAT_TREE_COUNT, .threshold = 21 },
     +	{ .name = CONCERN_STAT_BLOB_COUNT, .threshold = 22 },
    @@ builtin/repo.c: struct stats_table {
     +	{ 0 }
     +};
     +
    -+static size_t get_concern_threshold(const char *name, struct concern_stat_threshold *thresholds) {
    ++static int get_concern_threshold(const char *name, struct concern_stat_threshold *thresholds)
    ++{
     +	for (size_t i = 0; thresholds[i].name; i++) {
    -+		if (!strcmp(thresholds[i].name, name)) {
    ++		if (!strcmp(thresholds[i].name, name))
     +			return thresholds[i].threshold;
    -+		}
     +	}
    -+	return 0;
    ++	return -1;
     +}
     +
    -+static size_t repo_structure_concern_level(const char* concern_name,
    -+					   size_t value,
    -+					   struct concern_stat_threshold *thresholds) {
    -+	const size_t threshold = get_concern_threshold(concern_name, thresholds);
    -+	size_t log_value;
    -+	if (threshold == 0)
    -+		BUG("unexpected repository stat threshold: '%s'", concern_name);
    -+	log_value = log2u(value);
    -+	if (log_value > threshold)
    -+		return log_value - threshold;
    -+	else
    ++static int repo_structure_concern_level(const char *concern_name,
    ++					size_t value,
    ++					struct concern_stat_threshold *thresholds,
    ++					bool show_concern)
    ++{
    ++	if (show_concern) {
    ++		size_t log_value;
    ++		const int threshold = get_concern_threshold(concern_name, thresholds);
    ++		if (threshold < 0)
    ++			BUG("unexpected repository stat threshold: '%s'", concern_name);
    ++		log_value = (size_t)log2u(value);
    ++		if (log_value > (size_t)threshold)
    ++			return log_value - threshold;
    ++		else
    ++			return 0;
    ++	} else {
     +		return 0;
    ++	}
     +}
     +
      /*
    @@ builtin/repo.c: static void stats_table_vaddf(struct stats_table *table,
      		if (value_width > table->value_col_width)
      			table->value_col_width = value_width;
      	}
    -+	if ((int)entry->concern > table->concern_col_width)
    -+		table->concern_col_width = entry->concern;
    ++	if (entry->concern > table->max_concern_value)
    ++		table->max_concern_value = entry->concern;
    ++
      	if (entry->unit) {
      		int unit_width = utf8_strwidth(entry->unit);
      		if (unit_width > table->unit_col_width)
    @@ builtin/repo.c: static void stats_table_addf(struct stats_table *table, const ch
      	va_end(ap);
      }
      
    -+// define a symbol for 'no concern value for this stat'; this will
    -+// make the code below easier to read.
    ++/*
    ++ * Define a symbol for 'no concern value for this stat'; this will
    ++ * make the code below easier to read.
    ++ */
     +#define CONCERN_STAT_VALUE_NONE 0
     +
      static void stats_table_count_addf(struct stats_table *table, size_t value,
    @@ builtin/repo.c: static void stats_table_object_size_addf(struct stats_table *tab
      
      	/*
      	 * A NULL OID should not have a table annotation.
    +@@ builtin/repo.c: static inline size_t get_total_object_values(struct object_values *values)
    + }
    + 
    + static void stats_table_setup_structure(struct stats_table *table,
    +-					struct repo_structure *stats)
    ++					struct repo_structure *stats,
    ++					bool show_concern)
    + {
    + 	struct object_stats *objects = &stats->objects;
    + 	struct ref_stats *refs = &stats->refs;
     @@ builtin/repo.c: static void stats_table_setup_structure(struct stats_table *table,
      
      	ref_total = get_total_reference_count(refs);
    @@ builtin/repo.c: static void stats_table_setup_structure(struct stats_table *tabl
     +	stats_table_count_addf(table, refs->branches, CONCERN_STAT_VALUE_NONE,
     +			       "    * %s", _("Branches"));
     +	stats_table_count_addf(table, refs->tags,
    -+			       repo_structure_concern_level(
    -+				       CONCERN_STAT_TAG_COUNT,
    -+				       refs->tags,
    -+    			               repo_structure_concern_thresholds),
    ++			       CONCERN_STAT_VALUE_NONE,
     +			       "    * %s", _("Tags"));
     +	stats_table_count_addf(table, refs->remotes, CONCERN_STAT_VALUE_NONE,
     +			       "    * %s", _("Remotes"));
    @@ builtin/repo.c: static void stats_table_setup_structure(struct stats_table *tabl
     +			       repo_structure_concern_level(
     +				       CONCERN_STAT_COMMIT_COUNT,
     +				       objects->type_counts.commits,
    -+				       repo_structure_concern_thresholds),
    ++				       repo_structure_concern_thresholds,
    ++				       show_concern),
      			       "    * %s", _("Commits"));
      	stats_table_count_addf(table, objects->type_counts.trees,
     +			       repo_structure_concern_level(
     +				       CONCERN_STAT_TREE_COUNT,
     +				       objects->type_counts.trees,
    -+				       repo_structure_concern_thresholds),
    ++				       repo_structure_concern_thresholds,
    ++				       show_concern),
      			       "    * %s", _("Trees"));
      	stats_table_count_addf(table, objects->type_counts.blobs,
     +			       repo_structure_concern_level(
     +				       CONCERN_STAT_BLOB_COUNT,
     +				       objects->type_counts.blobs,
    -+				       repo_structure_concern_thresholds),
    ++				       repo_structure_concern_thresholds,
    ++			               show_concern),
      			       "    * %s", _("Blobs"));
      	stats_table_count_addf(table, objects->type_counts.tags,
     +			       repo_structure_concern_level(
     +				       CONCERN_STAT_TAG_COUNT,
     +				       objects->type_counts.tags,
    -+				       repo_structure_concern_thresholds),
    ++				       repo_structure_concern_thresholds,
    ++				       show_concern),
      			       "    * %s", _("Tags"));
      
      	inflated_object_total = get_total_object_values(&objects->inflated_sizes);
    @@ builtin/repo.c: static void stats_table_setup_structure(struct stats_table *tabl
     +			      repo_structure_concern_level(
     +				      CONCERN_STAT_COMMIT_INFLATED_SIZE,
     +				      objects->inflated_sizes.commits,
    -+				      repo_structure_concern_thresholds),
    ++				      repo_structure_concern_thresholds,
    ++				      show_concern),
      			      "    * %s", _("Commits"));
      	stats_table_size_addf(table, objects->inflated_sizes.trees,
     +			      repo_structure_concern_level(
     +				      CONCERN_STAT_TREE_INFLATED_SIZE,
     +				      objects->inflated_sizes.trees,
    -+				      repo_structure_concern_thresholds),
    ++				      repo_structure_concern_thresholds,
    ++				      show_concern),
      			      "    * %s", _("Trees"));
      	stats_table_size_addf(table, objects->inflated_sizes.blobs,
     +			      repo_structure_concern_level(
     +				      CONCERN_STAT_BLOB_INFLATED_SIZE,
     +				      objects->inflated_sizes.blobs,
    -+				      repo_structure_concern_thresholds),
    ++				      repo_structure_concern_thresholds,
    ++				      show_concern),
      			      "    * %s", _("Blobs"));
      	stats_table_size_addf(table, objects->inflated_sizes.tags,
     +			      repo_structure_concern_level(
     +				      CONCERN_STAT_TAG_INFLATED_SIZE,
     +				      objects->inflated_sizes.tags,
    -+				      repo_structure_concern_thresholds),
    ++				      repo_structure_concern_thresholds,
    ++				      show_concern),
      			      "    * %s", _("Tags"));
      
      	disk_object_total = get_total_object_values(&objects->disk_sizes);
    @@ builtin/repo.c: static void stats_table_setup_structure(struct stats_table *tabl
     +			      repo_structure_concern_level(
     +				      CONCERN_STAT_COMMIT_SIZE,
     +				      objects->disk_sizes.commits,
    -+				      repo_structure_concern_thresholds),
    ++				      repo_structure_concern_thresholds,
    ++				      show_concern),
      			      "    * %s", _("Commits"));
      	stats_table_size_addf(table, objects->disk_sizes.trees,
     +			      repo_structure_concern_level(
     +				      CONCERN_STAT_TREE_SIZE,
     +				      objects->disk_sizes.trees,
    -+				      repo_structure_concern_thresholds),
    ++				      repo_structure_concern_thresholds,
    ++				      show_concern),
      			      "    * %s", _("Trees"));
      	stats_table_size_addf(table, objects->disk_sizes.blobs,
     +			      repo_structure_concern_level(
     +				      CONCERN_STAT_BLOB_SIZE,
     +				      objects->disk_sizes.blobs,
    -+				      repo_structure_concern_thresholds),
    ++				      repo_structure_concern_thresholds,
    ++				      show_concern),
      			      "    * %s", _("Blobs"));
      	stats_table_size_addf(table, objects->disk_sizes.tags,
     +			      repo_structure_concern_level(
     +				      CONCERN_STAT_TAG_SIZE,
     +				      objects->disk_sizes.tags,
    -+				      repo_structure_concern_thresholds),
    ++				      repo_structure_concern_thresholds,
    ++				      show_concern),
      			      "    * %s", _("Tags"));
      
      	stats_table_addf(table, "");
    @@ builtin/repo.c: static void stats_table_setup_structure(struct stats_table *tabl
     +				     repo_structure_concern_level(
     +					     CONCERN_STAT_COMMIT_LARGEST,
     +					     objects->largest.commit_size.value,
    -+					     repo_structure_concern_thresholds),
    ++					     repo_structure_concern_thresholds,
    ++					     show_concern),
      				     "    * %s", _("Maximum size"));
      	stats_table_object_count_addf(table,
      				      &objects->largest.parent_count.oid,
    @@ builtin/repo.c: static void stats_table_setup_structure(struct stats_table *tabl
      				     objects->largest.tree_size.value,
     +				     repo_structure_concern_level(
     +					     CONCERN_STAT_TREE_LARGEST,
    -+					     objects->largest.commit_size.value,
    -+					     repo_structure_concern_thresholds),
    ++					     objects->largest.tree_size.value,
    ++					     repo_structure_concern_thresholds,
    ++					     show_concern),
      				     "    * %s", _("Maximum size"));
     +
      	stats_table_object_count_addf(table,
    @@ builtin/repo.c: static void stats_table_setup_structure(struct stats_table *tabl
     +				     repo_structure_concern_level(
     +					     CONCERN_STAT_BLOB_LARGEST,
     +					     objects->largest.blob_size.value,
    -+					     repo_structure_concern_thresholds),
    ++					     repo_structure_concern_thresholds,
    ++					     show_concern),
      				     "    * %s", _("Maximum size"));
      	stats_table_addf(table, "  * %s", _("Tags"));
      	stats_table_object_size_addf(table,
    @@ builtin/repo.c: static void stats_table_setup_structure(struct stats_table *tabl
     +				     repo_structure_concern_level(
     +					     CONCERN_STAT_TAG_LARGEST,
     +					     objects->largest.tag_size.value,
    -+					     repo_structure_concern_thresholds),
    ++					     repo_structure_concern_thresholds,
    ++					     show_concern),
      				     "    * %s", _("Maximum size"));
      }
      
    @@ builtin/repo.c: static void stats_table_setup_structure(struct stats_table *tabl
      {
      	const char *name_col_title = _("Repository structure");
      	const char *value_col_title = _("Value");
    -+	const char *concern_col_title = _("Concern");
    - 	int title_name_width = utf8_strwidth(name_col_title);
    - 	int title_value_width = utf8_strwidth(value_col_title);
    -+	int title_concern_width = utf8_strwidth(concern_col_title);
    +@@ builtin/repo.c: static void stats_table_print_structure(const struct stats_table *table)
      	int name_col_width = table->name_col_width;
      	int value_col_width = table->value_col_width;
      	int unit_col_width = table->unit_col_width;
    -+	int concern_col_width = table->concern_col_width;
    ++	const char *concern_col_title = _("Concern");
    ++	const size_t concern_title_width = strlen(concern_col_title);
    ++	size_t concern_col_width = (table->max_concern_value > concern_title_width) ? table->max_concern_value : concern_title_width;
      	struct string_list_item *item;
      	struct strbuf buf = STRBUF_INIT;
     +	struct strbuf concern_str = STRBUF_INIT;
    @@ builtin/repo.c: static void stats_table_setup_structure(struct stats_table *tabl
      	if (title_value_width > value_col_width + unit_col_width + 1)
      		value_col_width = title_value_width - unit_col_width;
     -
    -+	if (show_concern && title_concern_width > concern_col_width)
    -+		concern_col_width = title_concern_width;
      	strbuf_addstr(&buf, "| ");
      	strbuf_utf8_align(&buf, ALIGN_LEFT, name_col_width + INDEX_WIDTH,
      			  name_col_title);
    @@ builtin/repo.c: static void stats_table_print_structure(const struct stats_table
     +	printf(" |");
     +	if (show_concern) {
     +		putchar(' ');
    -+		for (int i = 0; i < concern_col_width; i++) {
    ++		for (size_t i = 0; i < concern_col_width; i++) {
     +			putchar('-');
     +		}
     +		printf(" |");
    @@ builtin/repo.c: static void stats_table_print_structure(const struct stats_table
      		printf("%s\n", buf.buf);
      	}
      
    +@@ builtin/repo.c: static void stats_table_print_structure(const struct stats_table *table)
    + 		for_each_string_list_item(item, &table->annotations)
    + 			printf("%s\n", item->string);
    + 	}
    +-
    ++	strbuf_release(&concern_str);
    + 	strbuf_release(&buf);
    + }
    + 
     @@ builtin/repo.c: static void print_object_data(const char *key, char key_delim,
      }
      
    @@ builtin/repo.c: static void print_object_data(const char *key, char key_delim,
      {
      	print_keyvalue("references.branches.count", key_delim,
      		       stats->refs.branches, value_delim);
    -+
    - 	print_keyvalue("references.tags.count", key_delim,
    - 		       stats->refs.tags, value_delim);
    -+
    - 	print_keyvalue("references.remotes.count", key_delim,
    +@@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stats,
      		       stats->refs.remotes, value_delim);
      	print_keyvalue("references.others.count", key_delim,
    -@@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stats,
    - 
    + 		       stats->refs.others, value_delim);
    +-
      	print_keyvalue("objects.commits.count", key_delim,
      		       stats->objects.type_counts.commits, value_delim);
     +	if (show_concern)
    @@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stat
     +			       repo_structure_concern_level(
     +				       CONCERN_STAT_COMMIT_COUNT,
     +				       stats->objects.type_counts.commits,
    -+				       repo_structure_concern_thresholds),
    ++				       repo_structure_concern_thresholds,
    ++				       show_concern),
     +			       value_delim);
      	print_keyvalue("objects.trees.count", key_delim,
      		       stats->objects.type_counts.trees, value_delim);
    @@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stat
     +			       repo_structure_concern_level(
     +				       CONCERN_STAT_TREE_COUNT,
     +				       stats->objects.type_counts.trees,
    -+				       repo_structure_concern_thresholds),
    ++				       repo_structure_concern_thresholds,
    ++				       show_concern),
     +			       value_delim);
      	print_keyvalue("objects.blobs.count", key_delim,
      		       stats->objects.type_counts.blobs, value_delim);
    @@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stat
     +			       repo_structure_concern_level(
     +				       CONCERN_STAT_BLOB_COUNT,
     +				       stats->objects.type_counts.blobs,
    -+				       repo_structure_concern_thresholds),
    ++				       repo_structure_concern_thresholds,
    ++				       show_concern),
     +		 value_delim);
      	print_keyvalue("objects.tags.count", key_delim,
     -		       stats->objects.type_counts.tags, value_delim);
    +-
     +		stats->objects.type_counts.tags, value_delim);
     +	if (show_concern)
     +		print_keyvalue("objects.tags.count.concern_level", key_delim,
     +			       repo_structure_concern_level(
     +				       CONCERN_STAT_TAG_COUNT,
     +				       stats->objects.type_counts.tags,
    -+				       repo_structure_concern_thresholds),
    -+		 	       value_delim);
    - 
    ++				       repo_structure_concern_thresholds,
    ++				       show_concern),
    ++			       value_delim);
      	print_keyvalue("objects.commits.inflated_size", key_delim,
     -		       stats->objects.inflated_sizes.commits, value_delim);
     +		stats->objects.inflated_sizes.commits, value_delim);
    @@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stat
     +			       repo_structure_concern_level(
     +				       CONCERN_STAT_COMMIT_INFLATED_SIZE,
     +				       stats->objects.inflated_sizes.commits,
    -+				       repo_structure_concern_thresholds),
    ++				       repo_structure_concern_thresholds,
    ++				       show_concern),
     +			       value_delim);
      	print_keyvalue("objects.trees.inflated_size", key_delim,
     -		       stats->objects.inflated_sizes.trees, value_delim);
    @@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stat
     +	if (show_concern)
     +		print_keyvalue("objects.trees.inflated_size.concern_level", key_delim,
     +			       repo_structure_concern_level(
    -+				       CONCERN_STAT_TREE_INFLATED_SIZE,
    -+				       stats->objects.inflated_sizes.trees,
    -+				       repo_structure_concern_thresholds),
    ++					CONCERN_STAT_TREE_INFLATED_SIZE,
    ++					stats->objects.inflated_sizes.trees,
    ++					repo_structure_concern_thresholds,
    ++					show_concern),
     +			       value_delim);
    -+
      	print_keyvalue("objects.blobs.inflated_size", key_delim,
      		       stats->objects.inflated_sizes.blobs, value_delim);
     +	if (show_concern)
    @@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stat
     +			       repo_structure_concern_level(
     +				       CONCERN_STAT_BLOB_INFLATED_SIZE,
     +				       stats->objects.inflated_sizes.blobs,
    -+				       repo_structure_concern_thresholds),
    ++				       repo_structure_concern_thresholds,
    ++				       show_concern),
     +			       value_delim);
      	print_keyvalue("objects.tags.inflated_size", key_delim,
      		       stats->objects.inflated_sizes.tags, value_delim);
    +-
     +	if (show_concern)
     +		print_keyvalue("objects.tags.inflated_size.concern_level", key_delim,
     +			       repo_structure_concern_level(
     +				       CONCERN_STAT_TAG_INFLATED_SIZE,
     +				       stats->objects.inflated_sizes.tags,
    -+				       repo_structure_concern_thresholds),
    ++				       repo_structure_concern_thresholds,
    ++				       show_concern),
     +			       value_delim);
    - 
      	print_keyvalue("objects.commits.disk_size", key_delim,
      		       stats->objects.disk_sizes.commits, value_delim);
     +	if (show_concern)
    @@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stat
     +			       repo_structure_concern_level(
     +				       CONCERN_STAT_COMMIT_SIZE,
     +				       stats->objects.disk_sizes.commits,
    -+				       repo_structure_concern_thresholds),
    ++				       repo_structure_concern_thresholds,
    ++				       show_concern),
     +			       value_delim);
      	print_keyvalue("objects.trees.disk_size", key_delim,
      		       stats->objects.disk_sizes.trees, value_delim);
    @@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stat
     +			       repo_structure_concern_level(
     +				       CONCERN_STAT_TREE_SIZE,
     +				       stats->objects.disk_sizes.trees,
    -+				       repo_structure_concern_thresholds),
    ++				       repo_structure_concern_thresholds,
    ++				       show_concern),
     +			       value_delim);
      	print_keyvalue("objects.blobs.disk_size", key_delim,
      		       stats->objects.disk_sizes.blobs, value_delim);
     +	if (show_concern)
    -+
     +		print_keyvalue("objects.blobs.disk_size.concern_level", key_delim,
     +			       repo_structure_concern_level(
     +				       CONCERN_STAT_BLOB_SIZE,
     +				       stats->objects.disk_sizes.blobs,
    -+				       repo_structure_concern_thresholds),
    ++				       repo_structure_concern_thresholds,
    ++				       show_concern),
     +			      value_delim);
      	print_keyvalue("objects.tags.disk_size", key_delim,
      		       stats->objects.disk_sizes.tags, value_delim);
    +-
     +	if (show_concern)
     +		print_keyvalue("objects.tags.disk_size.concern_level", key_delim,
     +			       repo_structure_concern_level(
     +				       CONCERN_STAT_TAG_SIZE,
     +				       stats->objects.disk_sizes.tags,
    -+				       repo_structure_concern_thresholds),
    ++				       repo_structure_concern_thresholds,
    ++				       show_concern),
     +			      value_delim);
    - 
      	print_object_data("objects.commits.max_size", key_delim,
      			  &stats->objects.largest.commit_size, value_delim);
     +	if (show_concern)
    @@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stat
     +			       repo_structure_concern_level(
     +				       CONCERN_STAT_COMMIT_LARGEST,
     +				       stats->objects.largest.commit_size.value,
    -+				       repo_structure_concern_thresholds),
    ++				       repo_structure_concern_thresholds,
    ++				       show_concern),
     +			       value_delim);
      	print_object_data("objects.trees.max_size", key_delim,
      			  &stats->objects.largest.tree_size, value_delim);
    @@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stat
     +			       repo_structure_concern_level(
     +				       CONCERN_STAT_TREE_LARGEST,
     +				       stats->objects.largest.tree_size.value,
    -+				       repo_structure_concern_thresholds),
    ++				       repo_structure_concern_thresholds,
    ++				       show_concern),
     +			       value_delim);
      	print_object_data("objects.blobs.max_size", key_delim,
      			  &stats->objects.largest.blob_size, value_delim);
    @@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stat
     +			       repo_structure_concern_level(
     +				       CONCERN_STAT_BLOB_LARGEST,
     +				       stats->objects.largest.blob_size.value,
    -+				       repo_structure_concern_thresholds),
    ++				       repo_structure_concern_thresholds,
    ++				       show_concern),
     +			       value_delim);
      	print_object_data("objects.tags.max_size", key_delim,
     -			  &stats->objects.largest.tag_size, value_delim);
    +-
     +		          &stats->objects.largest.tag_size, value_delim);
     +	if (show_concern)
     +		print_keyvalue("objects.tags.max_size.concern_level", key_delim,
     +			       repo_structure_concern_level(
     +				       CONCERN_STAT_TAG_LARGEST,
     +				       stats->objects.largest.tag_size.value,
    -+				       repo_structure_concern_thresholds),
    ++				       repo_structure_concern_thresholds,
    ++				       show_concern),
     +			       value_delim);
    - 
      	print_object_data("objects.commits.max_parents", key_delim,
      			  &stats->objects.largest.parent_count, value_delim);
    + 	print_object_data("objects.trees.max_entries", key_delim,
    + 			  &stats->objects.largest.tree_entries, value_delim);
    +-
    + 	fflush(stdout);
    + }
    + 
     @@ builtin/repo.c: static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
      	struct repo_structure stats = { 0 };
      	struct rev_info revs;
    @@ builtin/repo.c: static int cmd_repo_structure(int argc, const char **argv, const
      
      	if (show_progress < 0)
     @@ builtin/repo.c: static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
    + 
      	switch (format) {
      	case FORMAT_TABLE:
    - 		stats_table_setup_structure(&table, &stats);
    +-		stats_table_setup_structure(&table, &stats);
     -		stats_table_print_structure(&table);
    ++		stats_table_setup_structure(&table, &stats, show_concern);
     +		stats_table_print_structure(&table, show_concern);
      		break;
      	case FORMAT_NEWLINE_TERMINATED:
    @@ builtin/repo.c: static int cmd_repo_structure(int argc, const char **argv, const
      	default:
      		BUG("invalid output format");
     
    - ## repo.c (new) ##
    -@@
    -+#include "repo.h"
    -
      ## t/t1901-repo-structure.sh ##
     @@ t/t1901-repo-structure.sh: test_expect_success 'empty repository' '
    + 		|   * Tags                  |        |
    + 		|     * Maximum size        |    0 B |
      		EOF
    - 
    +-
      		git repo structure >out 2>err &&
    -+		cp out /tmp/test1-out &&
      
      		test_cmp expect out &&
    - 		test_line_count = 0 err
2:  2c1395b6f0 ! 2:  0a3b6415ec repo: make concern thresholds configurable
    @@ Documentation/config/repo.adoc (new)
     +	of all inflated tags.
     +
     +`repo.concern.commit-largest`::
    -+	Sets the value of the concern threshold the size of the largest
    ++	Sets the value of the concern threshold for the size of the largest
     +	commit.
     +
     +`repo.concern.tree-largest`::
    @@ Documentation/config/repo.adoc (new)
     +	Sets the value of the concern threshold for the size of the
     +	largest tag.
     +
    -+All of the concern thresholds are integer values for the
    -+floor of the base-2 log of the threshold value. The default values
    -+of each threshold are set to heuristically determined values.
    ++Thresholds are actually computed using log values. But for human convenience,
    ++they're set as integer values. If the integer value supplied isn't a power of
    ++two, the actual value will be rounded to the nearest power of two. For example,
    ++if the value in the configuration setting is 80,000, the actual threshold will
    ++be set to 65536; if instead, the value from the configuration was set to
    ++100,000, the threshold will be set to 131,072.
    ++
     
      ## Documentation/git-repo.adoc ##
     @@ Documentation/git-repo.adoc: statistic value. The value is considered concerning if it exceeds a heuristicall
    @@ Documentation/git-repo.adoc: using the `nul` format:
      linkgit:git-rev-parse[1]
     
      ## builtin/repo.c ##
    -@@
    - 
    - #include "builtin.h"
    - #include "commit.h"
    -+#include "config.h"
    - #include "environment.h"
    - #include "hash.h"
    - #include "hex.h"
    -@@
    - #define REPO_STRUCTURE_USAGE \
    - 	"git repo structure [--format=(table|lines|nul) | -z] [--[no-]concern]"
    - 
    -+#define REPO_STRUCTURE_CONFIG_PREFIX "repo.concern."
    -+
    - static const char *const repo_usage[] = {
    - 	REPO_INFO_USAGE,
    - 	REPO_STRUCTURE_USAGE,
    -@@ builtin/repo.c: static size_t repo_structure_concern_level(const char* concern_name,
    - 		return 0;
    +@@ builtin/repo.c: static int repo_structure_concern_level(const char *concern_name,
    + 		const int threshold = get_concern_threshold(concern_name, thresholds);
    + 		if (threshold < 0)
    + 			BUG("unexpected repository stat threshold: '%s'", concern_name);
    ++		if (!threshold)
    ++			return 0;
    + 		log_value = (size_t)log2u(value);
    + 		if (log_value > (size_t)threshold)
    + 			return log_value - threshold;
    +@@ builtin/repo.c: static int repo_structure_concern_level(const char *concern_name,
    + 	}
      }
      
    ++/*
    ++ * To make configuring thresholds as intuitive as possible, we set the
    ++ * internal threshold value to the _nearest_ power of two to the value
    ++ * the user specified. So if they set a threshold to 500, we should use
    ++ * a log value of 9, but log2u would return 8.
    ++ *
    ++ * A threshold value of 0 means "never flag concerns for this stat".
    ++ */
    ++static size_t nearest_log2_value(size_t v)
    ++{
    ++	size_t logval, power_of_two_floor, mid;
    ++	if (!v)
    ++		return 0;
    ++	logval = (size_t)log2u(v);
    ++	power_of_two_floor = (size_t)1 << logval;
    ++	mid = (power_of_two_floor + (power_of_two_floor << 1)) / 2;
    ++	return (v <= mid) ? logval : (logval + 1);
    ++}
    ++
     +/*
     + * Concern thresholds aren't universal. We try to set reasonable default
     + * values, but projects may want to set these thresholds differently. To
     + * support that, they can set the thresholds for their project in the
    -+ * project's git config, in the section "repo.structure.concern".
    -+  */
    -+static int config_concern_thresholds(const char *prefixed_var,
    ++ * project's git config, in the section "repo.concern".
    ++ *
    ++ * This is used by repo_config; the call site is guarded by show_concern,
    ++ * so the global threshold array is only modified when --concern is requested,
    ++ * and that will happen at most once.
    ++ */
    ++static int config_concern_thresholds(const char *config_val_name,
     +				     const char *str_val,
     +				     const struct config_context *ctx,
     +				     void *cb)
     +{
     +	struct concern_stat_threshold *thresholds = cb;
    -+	if (starts_with(prefixed_var, REPO_STRUCTURE_CONFIG_PREFIX)) {
    -+		const char *concern_name = &prefixed_var[strlen(REPO_STRUCTURE_CONFIG_PREFIX)];
    ++	const char *concern_name;
    ++	if (skip_prefix(config_val_name, REPO_STRUCTURE_CONFIG_PREFIX, &concern_name)) {
     +		for (int i = 0; thresholds[i].name; i++) {
     +			if (!strcmp(thresholds[i].name, concern_name)) {
    -+				int value = git_config_int(prefixed_var, str_val, ctx->kvi);
    -+				if (value < 0)
    -+					return -1;
    -+				thresholds[i].threshold = value;
    ++				int threshold = git_config_int(concern_name, str_val, ctx->kvi);
    ++				if (threshold < 0) {
    ++					warning(_("invalid concern threshold '%d': value must be 0 or more"),
    ++						threshold);
    ++					return 0;
    ++				}
    ++				thresholds[i].threshold = nearest_log2_value(threshold);
     +				return 0;
     +			}
     +		}
    -+		warning(_("Invalid concern threshold name: '%s'"), concern_name);
    -+		return 0;
    ++		warning(_("invalid concern threshold name: '%s'"), concern_name);
     +	}
     +	return 0;
     +}
    @@ builtin/repo.c: static size_t repo_structure_concern_level(const char* concern_n
      /*
       * Holds column data that gets stored for each row.
       */
    -@@ builtin/repo.c: static void stats_table_print_structure(const struct stats_table *table,
    - 			strbuf_utf8_align(&buf, ALIGN_LEFT, concern_col_width, concern_str.buf);
    - 			strbuf_addstr(&buf, " |");
    - 		}
    -+		strbuf_release(&concern_str);
    - 		printf("%s\n", buf.buf);
    - 	}
    - 
    -@@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stats,
    - 				       stats->objects.largest.tag_size.value,
    - 				       repo_structure_concern_thresholds),
    - 			       value_delim);
    --
    - 	print_object_data("objects.commits.max_parents", key_delim,
    - 			  &stats->objects.largest.parent_count, value_delim);
    - 	print_object_data("objects.trees.max_entries", key_delim,
     @@ builtin/repo.c: static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
      		usage(_("too many arguments"));
      	repo_init_revisions(repo, &revs, prefix);
      
     +	if (show_concern)
    -+                repo_config(repo, &config_concern_thresholds, &repo_structure_concern_thresholds);
    -+
    ++		repo_config(repo, &config_concern_thresholds,
    ++			    &repo_structure_concern_thresholds);
     +
      	if (show_progress < 0)
      		show_progress = isatty(2);
      
     
      ## t/t1901-repo-structure.sh ##
    -@@ t/t1901-repo-structure.sh: test_expect_success 'empty repository' '
    - 		EOF
    - 
    - 		git repo structure >out 2>err &&
    --		cp out /tmp/test1-out &&
    - 
    - 		test_cmp expect out &&
    - 		test_line_count = 0 err
     @@ t/t1901-repo-structure.sh: test_expect_success SHA1 'repository with references and objects' '
      	)
      '
    @@ t/t1901-repo-structure.sh: test_expect_success SHA1 'repository with references
     +		# test to fail; so we just turn it off.
     +		git config core.compression 0 &&
     +		# configure thresholds to get concern indications
    -+		git config repo.concern.commit-count 7 &&
    -+		git config repo.concern.commit-size 14 &&
    -+		git config repo.concern.tree-size 10 &&
    -+		git config repo.concern.blob-size 8 &&
    -+		git config repo.concern.blob-inflated-size 10 &&
    -+		git config repo.concern.tag-size 2 &&
    -+		git config repo.concern.commit-largest 4 &&
    -+		git config repo.concern.blob-largest 2 &&
    ++		git config repo.concern.commit-count 120 &&
    ++		git config repo.concern.commit-size 16384 &&
    ++		git config repo.concern.tree-size 1024 &&
    ++		git config repo.concern.blob-size 256 &&
    ++		git config repo.concern.blob-inflated-size 1024 &&
    ++		git config repo.concern.tag-size 4 &&
    ++		git config repo.concern.commit-largest 16 &&
    ++		git config repo.concern.blob-largest 4 &&
     +		test_commit_bulk 1005 &&
     +		git tag -a foo -m bar &&
     +		oid="$(git rev-parse HEAD)" &&
    @@ t/t1901-repo-structure.sh: test_expect_success SHA1 'lines and nul format' '
     +		cd repo &&
     +		# configure thresholds to get concern indications
     +		git config core.compression 0 &&
    -+		git config repo.concern.commit-count 7 &&
    -+		git config repo.concern.commit-size 14 &&
    -+		git config repo.concern.tree-size 10 &&
    -+		git config repo.concern.blob-size 8 &&
    -+		git config repo.concern.blob-inflated-size 10 &&
    -+		git config repo.concern.tag-size 2 &&
    -+		git config repo.concern.commit-largest 4 &&
    -+		git config repo.concern.blob-largest 2 &&
    ++		git config repo.concern.commit-count 100 &&
    ++		git config repo.concern.commit-size 16384 &&
    ++		git config repo.concern.tree-size 1024 &&
    ++		git config repo.concern.blob-size 256 &&
    ++		git config repo.concern.blob-inflated-size 1024 &&
    ++		git config repo.concern.tag-size 4 &&
    ++		git config repo.concern.commit-largest 16 &&
    ++		git config repo.concern.blob-largest 4 &&
     +		touch configured &&
     +		test_commit_bulk 1005 &&
     +		git tag -a foo -m bar &&

--- base-commit: 3bc0341126508f78f5869cbfc0005e987efdf0c7 change-id: 20261019-b4-repo-structure-concern-f1d6f6326095

Previous: Mark C. Chu-CarrollNext: Mark C. Chu-Carroll
Message 21 of 23 in “repo: add degree-of-concern reporting to structure”
  1. 0/3 repo: add degree-of-concern reporting to structureMark C. Chu-Carroll, Oct 21, 2026
  2. 1/3 repo: add level of concern reportingMark C. Chu-Carroll, Oct 21, 2026
  3. 2/3 repo: make concern thresholds configurableMark C. Chu-Carroll, Oct 21, 2026
  4. 3/3 repo: add documentation for level-of-concern.Mark C. Chu-Carroll, Oct 21, 2026
  5. 0/3 repo: add degree-of-concern reporting to structureMark C. Chu-Carroll, Oct 26, 2026
  6. 1/3 repo: add level of concern reportingMark C. Chu-Carroll, Oct 26, 2026
  7. Patrick SteinhardtOct 27, 2026
  8. 2/3 repo: make concern thresholds configurableMark C. Chu-Carroll, Oct 26, 2026
  9. Patrick SteinhardtOct 27, 2026
  10. 3/3 repo: add documentation for level-of-concern.Mark C. Chu-Carroll, Oct 26, 2026
  11. Patrick SteinhardtOct 27, 2026
  12. Mark C. Chu-CarrollOct 28, 2026
  13. 0/2 repo: add degree-of-concern reporting to structureMark C. Chu-Carroll, Oct 27, 2026
  14. 1/2 repo: add level of concern reportingMark C. Chu-Carroll, Oct 27, 2026
  15. 2/2 repo: make concern thresholds configurableMark C. Chu-Carroll, Oct 27, 2026
  16. 0/2 repo: add degree-of-concern reporting to structureMark C. Chu-Carroll, Oct 29, 2026
  17. 1/2 repo: add level of concern reportingMark C. Chu-Carroll, Oct 29, 2026
  18. Junio C HamanoOct 29, 2026
  19. Mark C. Chu-CarrollOct 29, 2026
  20. 2/2 repo: make concern thresholds configurableMark C. Chu-Carroll, Oct 29, 2026
  21. 0/2 repo: add degree-of-concern reporting to structureMark C. Chu-Carroll, Nov 2, 2026
  22. 1/2 repo: add level of concern reportingMark C. Chu-Carroll, Nov 2, 2026
  23. 2/2 repo: make concern thresholds configurableMark C. Chu-Carroll, Nov 2, 2026

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.