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

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

From
MCMark C. Chu-Carroll <markchucarroll@fastmail.com>
Date
Oct 29, 2026, 17:52 UTC
Message-ID
<20261029-b4-repo-structure-concern-v4-0-d813537ec617@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 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 |  59 ++++++
 Documentation/git-repo.adoc    |  27 ++-
 builtin/repo.c                 | 408 ++++++++++++++++++++++++++++++++++++++---
 repo.c                         |   1 +
 t/t1901-repo-structure.sh      | 137 ++++++++++++++
 5 files changed, 602 insertions(+), 30 deletions(-)
Range-diff versus v3:
1:  0f4e75e270 ! 1:  5f67be20e2 repo: add level of concern reporting
    @@ Commit message
         interpret the data - when should a value be considered
         concerning?
     
    -    Add an option to "git repo structure" that adds a "degree
    -    of concern" column to the structure report, which indicates
    -    when a stat values has reached a point where it might be
    -    concerning, and indicate how severe the concern should be.
    +    Add a new option to "git repo structure" which causes it
    +    to generate an additional column providing a level of concern
    +    for many values.
    +
    +    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 problematic was when a base 2 logarithm of a value was
    +    more than 1 standard deviation from the mean of the log_2 values in
    +    the sample set.
    +
    +    Signed-off-by: Mark C. Chu-Carroll <markchucarroll@fastmail.com>
     
      ## Documentation/git-repo.adoc ##
     @@ Documentation/git-repo.adoc: SYNOPSIS
    @@ Documentation/git-repo.adoc: supported:
     
      ## builtin/repo.c ##
     @@
    + #include "tree.h"
    + #include "tree-walk.h"
    + #include "utf8.h"
    ++#include "wrapper.h"
    + 
    + #define REPO_INFO_USAGE \
    + 	"git repo info [--format=(lines|nul) | -z] [--all | <key>...]", \
      	"git repo info --keys [--format=(lines|nul) | -z]"
      
      #define REPO_STRUCTURE_USAGE \
    @@ builtin/repo.c: struct stats_table {
     +	size_t threshold;
     +};
     +
    -+enum concern_stat {
    -+	CONCERN_STAT_COMMIT_COUNT,
    -+	CONCERN_STAT_TREE_COUNT,
    -+	CONCERN_STAT_BLOB_COUNT,
    -+	CONCERN_STAT_TAG_COUNT,
    -+	CONCERN_STAT_COMMIT_SIZE,
    -+	CONCERN_STAT_TREE_SIZE,
    -+	CONCERN_STAT_BLOB_SIZE,
    -+	CONCERN_STAT_TAG_SIZE,
    -+	CONCERN_STAT_COMMIT_LARGEST,
    -+	CONCERN_STAT_TREE_LARGEST,
    -+	CONCERN_STAT_BLOB_LARGEST,
    -+	CONCERN_STAT_TAG_LARGEST,
    -+	CONCERN_STAT_COMMIT_INFLATED_SIZE,
    -+	CONCERN_STAT_TREE_INFLATED_SIZE,
    -+	CONCERN_STAT_BLOB_INFLATED_SIZE,
    -+	CONCERN_STAT_TAG_INFLATED_SIZE,
    -+};
    ++#define CONCERN_STAT_COMMIT_COUNT "commit-count"
    ++#define CONCERN_STAT_TREE_COUNT "tree-count"
    ++#define CONCERN_STAT_BLOB_COUNT "blob-count"
    ++#define CONCERN_STAT_TAG_COUNT "tag-count"
    ++#define CONCERN_STAT_COMMIT_SIZE "commit-size"
    ++#define CONCERN_STAT_TREE_SIZE "tree-size"
    ++#define CONCERN_STAT_BLOB_SIZE "blob-size"
    ++#define CONCERN_STAT_TAG_SIZE "tag-size"
    ++#define CONCERN_STAT_COMMIT_LARGEST "commit-largest"
    ++#define CONCERN_STAT_TREE_LARGEST "tree-largest"
    ++#define CONCERN_STAT_BLOB_LARGEST "blob-largest"
    ++#define CONCERN_STAT_TAG_LARGEST "tag-largest"
    ++#define CONCERN_STAT_COMMIT_INFLATED_SIZE "commit-inflated-size"
    ++#define CONCERN_STAT_TREE_INFLATED_SIZE "tree-inflated-size"
    ++#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[] = {
    -+	{ .name = "commit_count", .threshold = 19 },
    -+	{.name = "tree_count", .threshold = 21 },
    -+	{ .name = "blob_count", .threshold = 22 },
    -+	{ .name = "tag_count", .threshold = 15 },
    -+	{ .name = "commit_size", .threshold = 28 },
    -+	{ .name = "tree_size", .threshold = 27 },
    -+	{ .name = "blob_size", .threshold = 36 },
    -+	{ .name = "tags_size", .threshold = 18 },
    -+	{ .name = "commit_largest", .threshold = 18  },
    -+	{ .name = "tree_largest", .threshold = 18 },
    -+	{ .name = "blob_largest", .threshold = 26 },
    -+	{ .name = "tag_largest", .threshold =  15 },
    -+	{ .name = "commit_inflated_size", .threshold = 28 },
    -+	{ .name = "tree_inflated_size", .threshold = 32 },
    -+	{ .name = "blob_inflated_size", .threshold = 34 },
    -+	{ .name = "tag_inflated_size", .threshold = 25}
    ++	{ .name = CONCERN_STAT_COMMIT_COUNT, .threshold = 19 },
    ++	{ .name = CONCERN_STAT_TREE_COUNT, .threshold = 21 },
    ++	{ .name = CONCERN_STAT_BLOB_COUNT, .threshold = 22 },
    ++	{ .name = CONCERN_STAT_TAG_COUNT, .threshold = 15 },
    ++	{ .name = CONCERN_STAT_COMMIT_SIZE, .threshold = 28 },
    ++	{ .name = CONCERN_STAT_TREE_SIZE, .threshold = 27 },
    ++	{ .name = CONCERN_STAT_BLOB_SIZE, .threshold = 36 },
    ++	{ .name = CONCERN_STAT_TAG_SIZE, .threshold = 18 },
    ++	{ .name = CONCERN_STAT_COMMIT_LARGEST, .threshold = 18  },
    ++	{ .name = CONCERN_STAT_TREE_LARGEST, .threshold = 18 },
    ++	{ .name = CONCERN_STAT_BLOB_LARGEST, .threshold = 26 },
    ++	{ .name = CONCERN_STAT_TAG_LARGEST, .threshold =  15 },
    ++	{ .name = CONCERN_STAT_COMMIT_INFLATED_SIZE, .threshold = 28 },
    ++	{ .name = CONCERN_STAT_TREE_INFLATED_SIZE, .threshold = 32 },
    ++	{ .name = CONCERN_STAT_BLOB_INFLATED_SIZE, .threshold = 34 },
    ++	{ .name = CONCERN_STAT_TAG_INFLATED_SIZE, .threshold = 25 },
    ++	{ 0 }
     +};
     +
    -+/*
    -+ * 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,
    -+				     const char *str_val,
    -+				     const struct config_context *ctx,
    -+				     void *cb UNUSED) {
    -+	const char *config_prefix = "repo.structure.concern.";
    -+	if (!strncmp(config_prefix, prefixed_var, strlen(config_prefix))) {
    -+		const char *concern_name = &prefixed_var[strlen(config_prefix)];
    -+		for (int i = 0; concern_stat_thresholds[i].name != 0; i++) {
    -+			if (!strcmp(concern_stat_thresholds[i].name, concern_name)) {
    -+				int value = git_config_int(prefixed_var, str_val, ctx->kvi);
    -+				if (value < 0)
    -+					return -1;
    -+				concern_stat_thresholds[i].threshold = value;
    -+				return 0;
    -+			}
    ++static size_t 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)) {
    ++			return thresholds[i].threshold;
     +		}
    -+		warning(_("Invalid concern threshold name: '%s'"), concern_name);
    -+		return 0;
     +	}
     +	return 0;
     +}
     +
    -+static size_t approx_log2(long value) {
    -+	int position = 0;
    -+	if (value <= 0) {
    -+		return 0;
    -+	}
    -+	while (value > 1) {
    -+		position++;
    -+		value >>= 1;
    -+	}
    -+	return position;
    -+}
    -+
    -+static size_t repo_structure_concern_level(enum concern_stat stat_id,
    -+					   size_t value) {
    ++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 (stat_id >= ARRAY_SIZE(concern_stat_thresholds))
    -+		BUG("unexpected repository stat type");
    -+	log_value = approx_log2(value);
    -+	if (log_value > concern_stat_thresholds[stat_id].threshold)
    -+		return log_value -
    -+			concern_stat_thresholds[stat_id].threshold;
    ++	if (threshold == 0)
    ++		BUG("unexpected repository stat threshold: '%s'", concern_name);
    ++	log_value = log2u(value);
    ++	if (log_value > threshold)
    ++		return log_value - threshold;
     +	else
     +		return 0;
     +}
    @@ builtin/repo.c: static void stats_table_vaddf(struct stats_table *table,
      		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 char *format, ...)
    + 	va_end(ap);
      }
      
    ++// 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,
     -				   const char *format, ...)
     +				   size_t concern, const char *format, ...)
    @@ builtin/repo.c: static void stats_table_setup_structure(struct stats_table *tabl
     -	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"));
    -+	stats_table_count_addf(table, ref_total, 0,
    +-
    ++	stats_table_count_addf(table, ref_total, CONCERN_STAT_VALUE_NONE,
     +			       "  * %s", _("Count"));
    -+	stats_table_count_addf(table, refs->branches, 0,
    ++	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),
    ++				       refs->tags,
    ++    			               repo_structure_concern_thresholds),
     +			       "    * %s", _("Tags"));
    -+	stats_table_count_addf(table, refs->remotes, 0,
    ++	stats_table_count_addf(table, refs->remotes, CONCERN_STAT_VALUE_NONE,
     +			       "    * %s", _("Remotes"));
    -+	stats_table_count_addf(table, refs->others, 0,
    ++	stats_table_count_addf(table, refs->others, CONCERN_STAT_VALUE_NONE,
     +			       "    * %s", _("Others"));
    - 
      	object_count_total = get_total_object_values(&objects->type_counts);
      	stats_table_addf(table, "");
      	stats_table_addf(table, "* %s", _("Reachable objects"));
     -	stats_table_count_addf(table, object_count_total, "  * %s", _("Count"));
    -+	stats_table_count_addf(table, object_count_total, 0,
    ++	stats_table_count_addf(table, object_count_total, CONCERN_STAT_VALUE_NONE,
     +			       "  * %s", _("Count"));
      	stats_table_count_addf(table, objects->type_counts.commits,
     +			       repo_structure_concern_level(
     +				       CONCERN_STAT_COMMIT_COUNT,
    -+				       objects->type_counts.commits),
    ++				       objects->type_counts.commits,
    ++				       repo_structure_concern_thresholds),
      			       "    * %s", _("Commits"));
      	stats_table_count_addf(table, objects->type_counts.trees,
     +			       repo_structure_concern_level(
     +				       CONCERN_STAT_TREE_COUNT,
    -+				       objects->type_counts.trees),
    ++				       objects->type_counts.trees,
    ++				       repo_structure_concern_thresholds),
      			       "    * %s", _("Trees"));
      	stats_table_count_addf(table, objects->type_counts.blobs,
     +			       repo_structure_concern_level(
     +				       CONCERN_STAT_BLOB_COUNT,
    -+				       objects->type_counts.blobs),
    ++				       objects->type_counts.blobs,
    ++				       repo_structure_concern_thresholds),
      			       "    * %s", _("Blobs"));
      	stats_table_count_addf(table, objects->type_counts.tags,
     +			       repo_structure_concern_level(
     +				       CONCERN_STAT_TAG_COUNT,
    -+				       objects->type_counts.tags),
    ++				       objects->type_counts.tags,
    ++				       repo_structure_concern_thresholds),
      			       "    * %s", _("Tags"));
      
      	inflated_object_total = get_total_object_values(&objects->inflated_sizes);
     -	stats_table_size_addf(table, inflated_object_total,
    -+	stats_table_size_addf(table, inflated_object_total, 0,
    ++	stats_table_size_addf(table, inflated_object_total, CONCERN_STAT_VALUE_NONE,
      			      "  * %s", _("Inflated size"));
      	stats_table_size_addf(table, objects->inflated_sizes.commits,
     +			      repo_structure_concern_level(
     +				      CONCERN_STAT_COMMIT_INFLATED_SIZE,
    -+				      objects->inflated_sizes.commits),
    ++				      objects->inflated_sizes.commits,
    ++				      repo_structure_concern_thresholds),
      			      "    * %s", _("Commits"));
      	stats_table_size_addf(table, objects->inflated_sizes.trees,
     +			      repo_structure_concern_level(
     +				      CONCERN_STAT_TREE_INFLATED_SIZE,
    -+				      objects->inflated_sizes.trees),
    ++				      objects->inflated_sizes.trees,
    ++				      repo_structure_concern_thresholds),
      			      "    * %s", _("Trees"));
      	stats_table_size_addf(table, objects->inflated_sizes.blobs,
     +			      repo_structure_concern_level(
     +				      CONCERN_STAT_BLOB_INFLATED_SIZE,
    -+				      objects->inflated_sizes.blobs),
    ++				      objects->inflated_sizes.blobs,
    ++				      repo_structure_concern_thresholds),
      			      "    * %s", _("Blobs"));
      	stats_table_size_addf(table, objects->inflated_sizes.tags,
     +			      repo_structure_concern_level(
     +				      CONCERN_STAT_TAG_INFLATED_SIZE,
    -+				      objects->inflated_sizes.tags),
    ++				      objects->inflated_sizes.tags,
    ++				      repo_structure_concern_thresholds),
      			      "    * %s", _("Tags"));
      
      	disk_object_total = get_total_object_values(&objects->disk_sizes);
     -	stats_table_size_addf(table, disk_object_total,
    -+	stats_table_size_addf(table, disk_object_total, 0,
    ++	stats_table_size_addf(table, disk_object_total, CONCERN_STAT_VALUE_NONE,
      			      "  * %s", _("Disk size"));
      	stats_table_size_addf(table, objects->disk_sizes.commits,
     +			      repo_structure_concern_level(
     +				      CONCERN_STAT_COMMIT_SIZE,
    -+				      objects->disk_sizes.commits),
    ++				      objects->disk_sizes.commits,
    ++				      repo_structure_concern_thresholds),
      			      "    * %s", _("Commits"));
      	stats_table_size_addf(table, objects->disk_sizes.trees,
     +			      repo_structure_concern_level(
     +				      CONCERN_STAT_TREE_SIZE,
    -+				      objects->disk_sizes.trees),
    ++				      objects->disk_sizes.trees,
    ++				      repo_structure_concern_thresholds),
      			      "    * %s", _("Trees"));
      	stats_table_size_addf(table, objects->disk_sizes.blobs,
     +			      repo_structure_concern_level(
     +				      CONCERN_STAT_BLOB_SIZE,
    -+				      objects->disk_sizes.blobs),
    ++				      objects->disk_sizes.blobs,
    ++				      repo_structure_concern_thresholds),
      			      "    * %s", _("Blobs"));
      	stats_table_size_addf(table, objects->disk_sizes.tags,
     +			      repo_structure_concern_level(
     +				      CONCERN_STAT_TAG_SIZE,
    -+				      objects->disk_sizes.tags),
    ++				      objects->disk_sizes.tags,
    ++				      repo_structure_concern_thresholds),
      			      "    * %s", _("Tags"));
      
      	stats_table_addf(table, "");
    @@ builtin/repo.c: static void stats_table_setup_structure(struct stats_table *tabl
      				     objects->largest.commit_size.value,
     +				     repo_structure_concern_level(
     +					     CONCERN_STAT_COMMIT_LARGEST,
    -+					     objects->largest.commit_size.value),
    ++					     objects->largest.commit_size.value,
    ++					     repo_structure_concern_thresholds),
      				     "    * %s", _("Maximum size"));
      	stats_table_object_count_addf(table,
      				      &objects->largest.parent_count.oid,
      				      objects->largest.parent_count.value,
    -+				      0,
    ++				      CONCERN_STAT_VALUE_NONE,
      				      "    * %s", _("Maximum parents"));
      	stats_table_addf(table, "  * %s", _("Trees"));
      	stats_table_object_size_addf(table,
      				     &objects->largest.tree_size.oid,
      				     objects->largest.tree_size.value,
    -+				      repo_structure_concern_level(
    -+					      CONCERN_STAT_TREE_LARGEST,
    -+					      objects->largest.tree_size.value),
    ++				     repo_structure_concern_level(
    ++					     CONCERN_STAT_TREE_LARGEST,
    ++					     objects->largest.commit_size.value,
    ++					     repo_structure_concern_thresholds),
      				     "    * %s", _("Maximum size"));
    ++
      	stats_table_object_count_addf(table,
    - 				      &objects->largest.tree_entries.oid,
    - 				      objects->largest.tree_entries.value,
    -+				      0,
    - 				      "    * %s", _("Maximum entries"));
    +-				      &objects->largest.tree_entries.oid,
    +-				      objects->largest.tree_entries.value,
    +-				      "    * %s", _("Maximum entries"));
    ++				     &objects->largest.tree_entries.oid,
    ++				     objects->largest.tree_entries.value,
    ++			             CONCERN_STAT_VALUE_NONE,
    ++				     "    * %s", _("Maximum entries"));
      	stats_table_addf(table, "  * %s", _("Blobs"));
      	stats_table_object_size_addf(table,
      				     &objects->largest.blob_size.oid,
      				     objects->largest.blob_size.value,
     +				     repo_structure_concern_level(
     +					     CONCERN_STAT_BLOB_LARGEST,
    -+					     objects->largest.blob_size.value),
    ++					     objects->largest.blob_size.value,
    ++					     repo_structure_concern_thresholds),
      				     "    * %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
      				     objects->largest.tag_size.value,
     +				     repo_structure_concern_level(
     +					     CONCERN_STAT_TAG_LARGEST,
    -+					     objects->largest.tag_size.value),
    ++					     objects->largest.tag_size.value,
    ++					     repo_structure_concern_thresholds),
      				     "    * %s", _("Maximum size"));
      }
      
    @@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stat
      		       stats->objects.type_counts.commits, value_delim);
     +	if (show_concern)
     +		print_keyvalue("objects.commits.count.concern_level", key_delim,
    -+			      repo_structure_concern_level(CONCERN_STAT_COMMIT_COUNT,
    -+							   stats->objects.type_counts.commits),
    -+			      value_delim);
    ++			       repo_structure_concern_level(
    ++				       CONCERN_STAT_COMMIT_COUNT,
    ++				       stats->objects.type_counts.commits,
    ++				       repo_structure_concern_thresholds),
    ++			       value_delim);
      	print_keyvalue("objects.trees.count", key_delim,
      		       stats->objects.type_counts.trees, value_delim);
     +	if (show_concern)
     +		print_keyvalue("objects.trees.count.concern_level", key_delim,
    -+			      repo_structure_concern_level(CONCERN_STAT_TREE_COUNT,
    -+							   stats->objects.type_counts.trees),
    -+			      value_delim);
    ++			       repo_structure_concern_level(
    ++				       CONCERN_STAT_TREE_COUNT,
    ++				       stats->objects.type_counts.trees,
    ++				       repo_structure_concern_thresholds),
    ++			       value_delim);
      	print_keyvalue("objects.blobs.count", key_delim,
      		       stats->objects.type_counts.blobs, value_delim);
     +	if (show_concern)
     +		print_keyvalue("objects.blobs.count.concern_level", key_delim,
    -+			      repo_structure_concern_level(CONCERN_STAT_BLOB_COUNT,
    -+							   stats->objects.type_counts.blobs),
    -+			      value_delim);
    ++			       repo_structure_concern_level(
    ++				       CONCERN_STAT_BLOB_COUNT,
    ++				       stats->objects.type_counts.blobs,
    ++				       repo_structure_concern_thresholds),
    ++		 value_delim);
      	print_keyvalue("objects.tags.count", key_delim,
    - 		       stats->objects.type_counts.tags, value_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),
    -+			      value_delim);
    ++			       repo_structure_concern_level(
    ++				       CONCERN_STAT_TAG_COUNT,
    ++				       stats->objects.type_counts.tags,
    ++				       repo_structure_concern_thresholds),
    ++		 	       value_delim);
      
      	print_keyvalue("objects.commits.inflated_size", key_delim,
    - 		       stats->objects.inflated_sizes.commits, value_delim);
    +-		       stats->objects.inflated_sizes.commits, value_delim);
    ++		stats->objects.inflated_sizes.commits, value_delim);
     +	if (show_concern)
     +		print_keyvalue("objects.commits.inflated_size.concern_level", key_delim,
    -+			       repo_structure_concern_level(CONCERN_STAT_COMMIT_INFLATED_SIZE,
    -+							    stats->objects.inflated_sizes.commits),
    -+			      value_delim);
    ++			       repo_structure_concern_level(
    ++				       CONCERN_STAT_COMMIT_INFLATED_SIZE,
    ++				       stats->objects.inflated_sizes.commits,
    ++				       repo_structure_concern_thresholds),
    ++			       value_delim);
      	print_keyvalue("objects.trees.inflated_size", key_delim,
    - 		       stats->objects.inflated_sizes.trees, value_delim);
    +-		       stats->objects.inflated_sizes.trees, value_delim);
    ++		stats->objects.inflated_sizes.trees, value_delim);
     +	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),
    -+			      value_delim);
    ++			       repo_structure_concern_level(
    ++				       CONCERN_STAT_TREE_INFLATED_SIZE,
    ++				       stats->objects.inflated_sizes.trees,
    ++				       repo_structure_concern_thresholds),
    ++			       value_delim);
     +
      	print_keyvalue("objects.blobs.inflated_size", key_delim,
      		       stats->objects.inflated_sizes.blobs, value_delim);
     +	if (show_concern)
     +		print_keyvalue("objects.blobs.inflated_size.concern_level", key_delim,
    -+			       repo_structure_concern_level(CONCERN_STAT_BLOB_INFLATED_SIZE,
    -+							    stats->objects.inflated_sizes.blobs),
    -+			      value_delim);
    ++			       repo_structure_concern_level(
    ++				       CONCERN_STAT_BLOB_INFLATED_SIZE,
    ++				       stats->objects.inflated_sizes.blobs,
    ++				       repo_structure_concern_thresholds),
    ++			       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),
    -+			      value_delim);
    ++			       repo_structure_concern_level(
    ++				       CONCERN_STAT_TAG_INFLATED_SIZE,
    ++				       stats->objects.inflated_sizes.tags,
    ++				       repo_structure_concern_thresholds),
    ++			       value_delim);
      
      	print_keyvalue("objects.commits.disk_size", key_delim,
      		       stats->objects.disk_sizes.commits, value_delim);
     +	if (show_concern)
     +		print_keyvalue("objects.commits.disk_size.concern_level", key_delim,
    -+			       repo_structure_concern_level(CONCERN_STAT_COMMIT_SIZE,
    -+							    stats->objects.disk_sizes.commits),
    -+			      value_delim);
    ++			       repo_structure_concern_level(
    ++				       CONCERN_STAT_COMMIT_SIZE,
    ++				       stats->objects.disk_sizes.commits,
    ++				       repo_structure_concern_thresholds),
    ++			       value_delim);
      	print_keyvalue("objects.trees.disk_size", key_delim,
      		       stats->objects.disk_sizes.trees, value_delim);
     +	if (show_concern)
     +		print_keyvalue("objects.trees.disk_size.concern_level", key_delim,
    -+			       repo_structure_concern_level(CONCERN_STAT_TREE_SIZE,
    -+							    stats->objects.disk_sizes.trees),
    -+			      value_delim);
    ++			       repo_structure_concern_level(
    ++				       CONCERN_STAT_TREE_SIZE,
    ++				       stats->objects.disk_sizes.trees,
    ++				       repo_structure_concern_thresholds),
    ++			       value_delim);
      	print_keyvalue("objects.blobs.disk_size", key_delim,
      		       stats->objects.disk_sizes.blobs, value_delim);
     +	if (show_concern)
    -+		print_keyvalue("objects.blobs.blob_size.concern_level", key_delim,
    -+			       repo_structure_concern_level(CONCERN_STAT_BLOB_SIZE,
    -+							    stats->objects.disk_sizes.blobs),
    ++
    ++		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),
     +			      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_level(
    ++				       CONCERN_STAT_TAG_SIZE,
    ++				       stats->objects.disk_sizes.tags,
    ++				       repo_structure_concern_thresholds),
     +			      value_delim);
      
      	print_object_data("objects.commits.max_size", key_delim,
      			  &stats->objects.largest.commit_size, value_delim);
     +	if (show_concern)
     +		print_keyvalue("objects.commits.max_size.concern_level", key_delim,
    -+			       repo_structure_concern_level(CONCERN_STAT_COMMIT_LARGEST,
    -+							    stats->objects.largest.commit_size.value),
    -+			      value_delim);
    ++			       repo_structure_concern_level(
    ++				       CONCERN_STAT_COMMIT_LARGEST,
    ++				       stats->objects.largest.commit_size.value,
    ++				       repo_structure_concern_thresholds),
    ++			       value_delim);
      	print_object_data("objects.trees.max_size", key_delim,
      			  &stats->objects.largest.tree_size, value_delim);
     +	if (show_concern)
     +		print_keyvalue("objects.trees.max_size.concern_level", key_delim,
    -+			       repo_structure_concern_level(CONCERN_STAT_TREE_LARGEST,
    -+							    stats->objects.largest.tree_size.value),
    -+			      value_delim);
    ++			       repo_structure_concern_level(
    ++				       CONCERN_STAT_TREE_LARGEST,
    ++				       stats->objects.largest.tree_size.value,
    ++				       repo_structure_concern_thresholds),
    ++			       value_delim);
      	print_object_data("objects.blobs.max_size", key_delim,
      			  &stats->objects.largest.blob_size, value_delim);
     +	if (show_concern)
     +		print_keyvalue("objects.blobs.max_size.concern_level", key_delim,
    -+			       repo_structure_concern_level(CONCERN_STAT_BLOB_LARGEST,
    -+							    stats->objects.largest.blob_size.value),
    -+			      value_delim);
    ++			       repo_structure_concern_level(
    ++				       CONCERN_STAT_BLOB_LARGEST,
    ++				       stats->objects.largest.blob_size.value,
    ++				       repo_structure_concern_thresholds),
    ++			       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);
    ++		          &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),
    -+			      value_delim);
    ++			       repo_structure_concern_level(
    ++				       CONCERN_STAT_TAG_LARGEST,
    ++				       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);
    @@ builtin/repo.c: static int cmd_repo_structure(int argc, const char **argv, const
      		OPT_END()
      	};
      
    + 	argc = parse_options(argc, argv, prefix, options, repo_structure_usage, 0);
    + 	if (argc)
    + 		usage(_("too many arguments"));
    +-
    + 	repo_init_revisions(repo, &revs, prefix);
    + 
    + 	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:
    @@ builtin/repo.c: static int cmd_repo_structure(int argc, const char **argv, const
      	default:
      		BUG("invalid output format");
     
    - ## sideband.c ##
    + ## repo.c (new) ##
     @@
    --#define USE_THE_REPOSITORY_VARIABLE
    -+define USE_THE_REPOSITORY_VARIABLE
    - #define DISABLE_SIGN_COMPARE_WARNINGS
    - 
    - #include "git-compat-util.h"
    ++#include "repo.h"
     
      ## t/t1901-repo-structure.sh ##
     @@ t/t1901-repo-structure.sh: test_expect_success 'empty repository' '
      		EOF
      
      		git repo structure >out 2>err &&
    --
    --		test_cmp expect out &&
    --		test_line_count = 0 err
    --	)
    --'
    --
    --test_expect_success SHA1 'repository with references and objects' '
    --	test_when_finished "rm -rf repo" &&
    --	git init repo &&
    --	(
    --		cd repo &&
    --		test_commit_bulk 1005 &&
    --		git tag -a foo -m bar &&
    --
    --		oid="$(git rev-parse HEAD)" &&
    --		git update-ref refs/remotes/origin/foo "$oid" &&
    --
    --		# Also creates a commit, tree, and blob.
    --		git notes add -m foo &&
    --
    --		# The tags disk size is handled specially due to the
    --		# git-rev-list(1) --disk-usage=human option printing the full
    --		# "byte/bytes" unit string instead of just "B".
    --		cat >expect <<-EOF &&
    --		| Repository structure      | Value      |
    --		| ------------------------- | ---------- |
    --		| * References              |            |
    --		|   * Count                 |      4     |
    --		|     * Branches            |      1     |
    --		|     * Tags                |      1     |
    --		|     * Remotes             |      1     |
    --		|     * Others              |      1     |
    --		|                           |            |
    --		| * Reachable objects       |            |
    --		|   * Count                 |   3.02 k   |
    --		|     * Commits             |   1.01 k   |
    --		|     * Trees               |   1.01 k   |
    --		|     * Blobs               |   1.01 k   |
    --		|     * Tags                |      1     |
    --		|   * Inflated size         |  16.03 MiB |
    --		|     * Commits             | 217.92 KiB |
    --		|     * Trees               |  15.81 MiB |
    --		|     * Blobs               |  11.68 KiB |
    --		|     * Tags                |    132 B   |
    --		|   * Disk size             | $(object_type_disk_usage all true) |
    --		|     * Commits             | $(object_type_disk_usage commit true) |
    --		|     * Trees               | $(object_type_disk_usage tree true) |
    --		|     * Blobs               |  $(object_type_disk_usage blob true) |
    --		|     * Tags                |    $(object_type_disk_usage tag) B   |
    --		|                           |            |
    --		| * Largest objects         |            |
    --		|   * Commits               |            |
    --		|     * Maximum size    [1] |    223 B   |
    --		|     * Maximum parents [2] |      1     |
    --		|   * Trees                 |            |
    --		|     * Maximum size    [3] |  32.29 KiB |
    --		|     * Maximum entries [4] |   1.01 k   |
    --		|   * Blobs                 |            |
    --		|     * Maximum size    [5] |     13 B   |
    --		|   * Tags                  |            |
    --		|     * Maximum size    [6] |    132 B   |
    --
    --		[1] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a
    --		[2] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a
    --		[3] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c
    --		[4] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c
    --		[5] 97d808e45116bf02103490294d3d46dad7a2ac62
    --		[6] 4dae4f5954f5e6feb3577cfb1b181daa3fd3afd2
    --		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 'lines and nul format' '
    - 	)
    - '
    - 
    -+test_expect_success SHA1 'lines format with concerns' '
    -+	test_when_finished "rm -rf repo" &&
    -+	git init repo &&
    -+	(
    -+		cd repo &&
    -+		# configure thresholds to get concern indications
    -+		git config repo.structure.concern.commit-count 7 &&
    -+		git config repo.structure.concern.commit-size 14 &&
    -+		git config repo.structure.concern.tree-size 10 &&
    -+		git config repo.structure.concern.blob-size 8 &&
    -+		git config repo.structure.concern.blob-inflated-size 10 &&
    -+		git config repo.structure.concern.tag-size 2 &&
    -+		git config repo.structure.concern.commit-largest 4 &&
    -+		git config repo.structure.concern.blob-largest 2 &&
    -+		touch configured &&
    -+		test_commit_bulk 1005 &&
    -+		git tag -a foo -m bar &&
    -+		oid="$(git rev-parse HEAD)" &&
    -+		git update-ref refs/remotes/origin/foo "$oid" &&
    -+
    -+		# Also creates a commit, tree, and blob.
    -+		git notes add -m foo &&
    -+
    -+
    -+		# The tags disk size is handled specially due to the
    -+		# git-rev-list(1) --disk-usage=human option printing the full
    -+		# "byte/bytes" unit string instead of just "B".
    -+		cat >expect <<-EOF &&
    -+		objects.commits.count.concern_level=2
    -+		objects.trees.count.concern_level=0
    -+		objects.blobs.count.concern_level=0
    -+		objects.tags.count.concern_level=0
    -+		objects.commits.inflated_size.concern_level=0
    -+		objects.trees.inflated_size.concern_level=0
    -+		objects.blobs.inflated_size.concern_level=3
    -+		objects.tags.inflated_size.concern_level=0
    -+		objects.commits.disk_size.concern_level=3
    -+		objects.trees.disk_size.concern_level=8
    -+		objects.blobs.blob_size.concern_level=6
    -+		objects.tags.disk_size.concern_level=4
    -+		objects.commits.max_size.concern_level=3
    -+		objects.trees.max_size.concern_level=0
    -+		objects.blobs.max_size.concern_level=1
    -+		objects.tags.max_size.concern_level=0
    -+		EOF
    -+
    -+		git repo structure --concern --format=lines >actual 2>err &&
    -+		test_grep "concern_level" actual >actual-concerns &&
    -+		test_cmp expect actual-concerns &&
    -+		test_line_count = 0 err
    -+	)
    -+'
    -+
    - test_expect_success 'progress meter option' '
    - 	test_when_finished "rm -rf repo" &&
    - 	git init repo &&
2:  56fb5590ea ! 2:  f0e25fae09 repo: make concern thresholds configurable
    @@ Commit message
         configure per-repo concern levels for "git repo structure"
         concern reporting.
     
    +    Signed-off-by: Mark C. Chu-Carroll <markchucarroll@fastmail.com>
    +
      ## Documentation/config/repo.adoc (new) ##
     @@
     +`repo.concern.commit-count`::
    @@ builtin/repo.c
      static const char *const repo_usage[] = {
      	REPO_INFO_USAGE,
      	REPO_STRUCTURE_USAGE,
    -@@ builtin/repo.c: enum concern_stat {
    - 	CONCERN_STAT_TREE_INFLATED_SIZE,
    - 	CONCERN_STAT_BLOB_INFLATED_SIZE,
    - 	CONCERN_STAT_TAG_INFLATED_SIZE,
    -+	CONCERN_STAT_MAXIMUM
    - };
    - 
    --struct concern_stat_threshold repo_structure_concern_thresholds[] = {
    --	{ .name = "commit_count", .threshold = 19 },
    --	{.name = "tree_count", .threshold = 21 },
    --	{ .name = "blob_count", .threshold = 22 },
    --	{ .name = "tag_count", .threshold = 15 },
    --	{ .name = "commit_size", .threshold = 28 },
    --	{ .name = "tree_size", .threshold = 27 },
    --	{ .name = "blob_size", .threshold = 36 },
    --	{ .name = "tags_size", .threshold = 18 },
    --	{ .name = "commit_largest", .threshold = 18  },
    --	{ .name = "tree_largest", .threshold = 18 },
    --	{ .name = "blob_largest", .threshold = 26 },
    --	{ .name = "tag_largest", .threshold =  15 },
    --	{ .name = "commit_inflated_size", .threshold = 28 },
    --	{ .name = "tree_inflated_size", .threshold = 32 },
    --	{ .name = "blob_inflated_size", .threshold = 34 },
    --	{ .name = "tag_inflated_size", .threshold = 25}
    -+struct concern_stat_threshold concern_stat_thresholds[] = {
    -+	{ .name = "commit-count", .threshold = 19 },
    -+	{ .name = "tree-count", .threshold = 21 },
    -+	{ .name = "blob-count", .threshold = 22 },
    -+	{ .name = "tag-count", .threshold = 15 },
    -+	{ .name = "commit-size", .threshold = 28 },
    -+	{ .name = "tree-size", .threshold = 27 },
    -+	{ .name = "blob-size", .threshold = 36 },
    -+	{ .name = "tag-size", .threshold = 18 },
    -+	{ .name = "commit-largest", .threshold = 18  },
    -+	{ .name = "tree-largest", .threshold = 18 },
    -+	{ .name = "blob-largest", .threshold = 26 },
    -+	{ .name = "tag-largest", .threshold =  15 },
    -+	{ .name = "commit-inflated-size", .threshold = 28 },
    -+	{ .name = "tree-inflated-size", .threshold = 32 },
    -+	{ .name = "blob-inflated-size", .threshold = 34 },
    -+	{ .name = "tag-inflated-size", .threshold = 25},
    -+	{ 0 }
    - };
    - 
    - /*
    -  * 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".
    -+ * project's git config, in the section "repo.concern".
    -   */
    - static int config_concern_thresholds(const char *prefixed_var,
    - 				     const char *str_val,
    - 				     const struct config_context *ctx,
    - 				     void *cb UNUSED) {
    --	const char *config_prefix = "repo.structure.concern.";
    --	if (!strncmp(config_prefix, prefixed_var, strlen(config_prefix))) {
    --		const char *concern_name = &prefixed_var[strlen(config_prefix)];
    -+	if (!strncmp(REPO_STRUCTURE_CONFIG_PREFIX, prefixed_var,
    -+	      strlen(REPO_STRUCTURE_CONFIG_PREFIX))) {
    -+		const char *concern_name =
    -+			&prefixed_var[strlen(REPO_STRUCTURE_CONFIG_PREFIX)];
    - 		for (int i = 0; concern_stat_thresholds[i].name != 0; i++) {
    - 			if (!strcmp(concern_stat_thresholds[i].name, concern_name)) {
    - 				int value = git_config_int(prefixed_var, str_val, ctx->kvi);
    -@@ builtin/repo.c: static int config_concern_thresholds(const char *prefixed_var,
    - 	return 0;
    +@@ builtin/repo.c: static size_t repo_structure_concern_level(const char* concern_name,
    + 		return 0;
      }
      
    --static size_t approx_log2(long value) {
    -+static size_t approx_log2(size_t value) {
    - 	int position = 0;
    - 	if (value <= 0) {
    - 		return 0;
    -@@ builtin/repo.c: static size_t approx_log2(long value) {
    - static size_t repo_structure_concern_level(enum concern_stat stat_id,
    - 					   size_t value) {
    - 	size_t log_value;
    --	if (stat_id >= ARRAY_SIZE(concern_stat_thresholds))
    -+	if (stat_id >= CONCERN_STAT_MAXIMUM)
    - 		BUG("unexpected repository stat type");
    - 	log_value = approx_log2(value);
    - 	if (log_value > concern_stat_thresholds[stat_id].threshold)
    ++/*
    ++ * 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,
    ++				     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)];
    ++		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;
    ++				return 0;
    ++			}
    ++		}
    ++		warning(_("Invalid concern threshold name: '%s'"), concern_name);
    ++		return 0;
    ++	}
    ++	return 0;
    ++}
    ++
    + /*
    +  * 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, " |");
    @@ builtin/repo.c: static void stats_table_print_structure(const struct stats_table
      	}
      
     @@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stats,
    - 	print_keyvalue("objects.blobs.disk_size", key_delim,
    - 		       stats->objects.disk_sizes.blobs, value_delim);
    - 	if (show_concern)
    --		print_keyvalue("objects.blobs.blob_size.concern_level", key_delim,
    -+		print_keyvalue("objects.blobs.disk_size.concern_level", key_delim,
    - 			       repo_structure_concern_level(CONCERN_STAT_BLOB_SIZE,
    - 							    stats->objects.disk_sizes.blobs),
    - 			      value_delim);
    -@@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stats,
    - 							    stats->objects.largest.commit_size.value),
    - 			      value_delim);
    - 	print_object_data("objects.trees.max_size", key_delim,
    --			  &stats->objects.largest.tree_size, value_delim);
    -+			 &stats->objects.largest.tree_size, value_delim);
    - 	if (show_concern)
    - 		print_keyvalue("objects.trees.max_size.concern_level", key_delim,
    - 			       repo_structure_concern_level(CONCERN_STAT_TREE_LARGEST,
    -@@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stats,
    - 							    stats->objects.largest.blob_size.value),
    - 			      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),
    + 			       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,
    - 	argc = parse_options(argc, argv, prefix, options, repo_structure_usage, 0);
    - 	if (argc)
      		usage(_("too many arguments"));
    --
    -+	if (show_concern)
    -+		repo_config(repo, &config_concern_thresholds, NULL);
      	repo_init_revisions(repo, &revs, prefix);
      
    ++	if (show_concern)
    ++                repo_config(repo, &config_concern_thresholds, &repo_structure_concern_thresholds);
    ++
    ++
      	if (show_progress < 0)
    -
    - ## sideband.c ##
    -@@
    --define USE_THE_REPOSITORY_VARIABLE
    -+#define USE_THE_REPOSITORY_VARIABLE
    - #define DISABLE_SIGN_COMPARE_WARNINGS
    + 		show_progress = isatty(2);
      
    - #include "git-compat-util.h"
     
      ## t/t1901-repo-structure.sh ##
     @@ t/t1901-repo-structure.sh: test_expect_success 'empty repository' '
    @@ t/t1901-repo-structure.sh: test_expect_success 'empty repository' '
      
      		test_cmp expect out &&
      		test_line_count = 0 err
    +@@ t/t1901-repo-structure.sh: test_expect_success SHA1 'repository with references and objects' '
      	)
      '
      
    -+test_expect_success SHA1 'repository with references and objects' '
    -+	test_when_finished "rm -rf repo" &&
    -+	git init repo &&
    -+	(
    -+		cd repo &&
    -+		test_commit_bulk 1005 &&
    -+		git tag -a foo -m bar &&
    -+
    -+		oid="$(git rev-parse HEAD)" &&
    -+		git update-ref refs/remotes/origin/foo "$oid" &&
    -+
    -+		# Also creates a commit, tree, and blob.
    -+		git notes add -m foo &&
    -+
    -+		# The tags disk size is handled specially due to the
    -+		# git-rev-list(1) --disk-usage=human option printing the full
    -+		# "byte/bytes" unit string instead of just "B".
    -+		cat >expect <<-EOF &&
    -+		| Repository structure      | Value      |
    -+		| ------------------------- | ---------- |
    -+		| * References              |            |
    -+		|   * Count                 |      4     |
    -+		|     * Branches            |      1     |
    -+		|     * Tags                |      1     |
    -+		|     * Remotes             |      1     |
    -+		|     * Others              |      1     |
    -+		|                           |            |
    -+		| * Reachable objects       |            |
    -+		|   * Count                 |   3.02 k   |
    -+		|     * Commits             |   1.01 k   |
    -+		|     * Trees               |   1.01 k   |
    -+		|     * Blobs               |   1.01 k   |
    -+		|     * Tags                |      1     |
    -+		|   * Inflated size         |  16.03 MiB |
    -+		|     * Commits             | 217.92 KiB |
    -+		|     * Trees               |  15.81 MiB |
    -+		|     * Blobs               |  11.68 KiB |
    -+		|     * Tags                |    132 B   |
    -+		|   * Disk size             | $(object_type_disk_usage all true) |
    -+		|     * Commits             | $(object_type_disk_usage commit true) |
    -+		|     * Trees               | $(object_type_disk_usage tree true) |
    -+		|     * Blobs               |  $(object_type_disk_usage blob true) |
    -+		|     * Tags                |    $(object_type_disk_usage tag) B   |
    -+		|                           |            |
    -+		| * Largest objects         |            |
    -+		|   * Commits               |            |
    -+		|     * Maximum size    [1] |    223 B   |
    -+		|     * Maximum parents [2] |      1     |
    -+		|   * Trees                 |            |
    -+		|     * Maximum size    [3] |  32.29 KiB |
    -+		|     * Maximum entries [4] |   1.01 k   |
    -+		|   * Blobs                 |            |
    -+		|     * Maximum size    [5] |     13 B   |
    -+		|   * Tags                  |            |
    -+		|     * Maximum size    [6] |    132 B   |
    -+
    -+		[1] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a
    -+		[2] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a
    -+		[3] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c
    -+		[4] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c
    -+		[5] 97d808e45116bf02103490294d3d46dad7a2ac62
    -+		[6] 4dae4f5954f5e6feb3577cfb1b181daa3fd3afd2
    -+		EOF
    -+
    -+		git repo structure >out 2>err &&
    -+
    -+		test_cmp expect out &&
    -+		test_line_count = 0 err
    -+	)
    -+'
    -+
     +test_expect_success SHA1 'concern flagging' '
     +	test_when_finished "rm -rf repo" &&
     +	git init repo &&
    @@ t/t1901-repo-structure.sh: test_expect_success 'empty repository' '
      test_expect_success SHA1 'lines and nul format' '
      	test_when_finished "rm -rf repo" &&
      	git init repo &&
    -@@ t/t1901-repo-structure.sh: test_expect_success SHA1 'lines format with concerns' '
    - 	git init repo &&
    - 	(
    - 		cd repo &&
    -+		# differences between zlib and zlib-ng can cause this
    -+		# test to fail; so we just turn it off.
    +@@ t/t1901-repo-structure.sh: test_expect_success SHA1 'lines and nul format' '
    + 	)
    + '
    + 
    ++test_expect_success SHA1 'lines format with concerns' '
    ++	test_when_finished "rm -rf repo" &&
    ++	git init repo &&
    ++	(
    ++		cd repo &&
    ++		# configure thresholds to get concern indications
     +		git config core.compression 0 &&
    - 		# configure thresholds to get concern indications
    --		git config repo.structure.concern.commit-count 7 &&
    --		git config repo.structure.concern.commit-size 14 &&
    --		git config repo.structure.concern.tree-size 10 &&
    --		git config repo.structure.concern.blob-size 8 &&
    --		git config repo.structure.concern.blob-inflated-size 10 &&
    --		git config repo.structure.concern.tag-size 2 &&
    --		git config repo.structure.concern.commit-largest 4 &&
    --		git config repo.structure.concern.blob-largest 2 &&
    --		touch configured &&
     +		git config repo.concern.commit-count 7 &&
     +		git config repo.concern.commit-size 14 &&
     +		git config repo.concern.tree-size 10 &&
    @@ t/t1901-repo-structure.sh: test_expect_success SHA1 'lines format with concerns'
     +		git config repo.concern.tag-size 2 &&
     +		git config repo.concern.commit-largest 4 &&
     +		git config repo.concern.blob-largest 2 &&
    - 		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 format with concerns' '
    - 		# Also creates a commit, tree, and blob.
    - 		git notes add -m foo &&
    - 
    --
    - 		# The tags disk size is handled specially due to the
    - 		# git-rev-list(1) --disk-usage=human option printing the full
    - 		# "byte/bytes" unit string instead of just "B".
    -@@ t/t1901-repo-structure.sh: test_expect_success SHA1 'lines format with concerns' '
    - 		objects.tags.inflated_size.concern_level=0
    - 		objects.commits.disk_size.concern_level=3
    - 		objects.trees.disk_size.concern_level=8
    --		objects.blobs.blob_size.concern_level=6
    --		objects.tags.disk_size.concern_level=4
    ++		touch configured &&
    ++		test_commit_bulk 1005 &&
    ++		git tag -a foo -m bar &&
    ++		oid="$(git rev-parse HEAD)" &&
    ++		git update-ref refs/remotes/origin/foo "$oid" &&
    ++
    ++		# Also creates a commit, tree, and blob.
    ++		git notes add -m foo &&
    ++
    ++
    ++		# The tags disk size is handled specially due to the
    ++		# git-rev-list(1) --disk-usage=human option printing the full
    ++		# "byte/bytes" unit string instead of just "B".
    ++		cat >expect <<-EOF &&
    ++		objects.commits.count.concern_level=2
    ++		objects.trees.count.concern_level=0
    ++		objects.blobs.count.concern_level=0
    ++		objects.tags.count.concern_level=0
    ++		objects.commits.inflated_size.concern_level=0
    ++		objects.trees.inflated_size.concern_level=0
    ++		objects.blobs.inflated_size.concern_level=3
    ++		objects.tags.inflated_size.concern_level=0
    ++		objects.commits.disk_size.concern_level=3
    ++		objects.trees.disk_size.concern_level=8
     +		objects.blobs.disk_size.concern_level=6
     +		objects.tags.disk_size.concern_level=5
    - 		objects.commits.max_size.concern_level=3
    - 		objects.trees.max_size.concern_level=0
    - 		objects.blobs.max_size.concern_level=1
    ++		objects.commits.max_size.concern_level=3
    ++		objects.trees.max_size.concern_level=0
    ++		objects.blobs.max_size.concern_level=1
    ++		objects.tags.max_size.concern_level=0
    ++		EOF
    ++
    ++		git repo structure --concern --format=lines >actual 2>err &&
    ++		test_grep "concern_level" actual >actual-concerns &&
    ++		test_cmp expect actual-concerns &&
    ++		test_line_count = 0 err
    ++	)
    ++'
    ++
    + test_expect_success 'progress meter option' '
    + 	test_when_finished "rm -rf repo" &&
    + 	git init repo &&

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

Previous: Mark C. Chu-CarrollNext: Mark C. Chu-Carroll
Message 16 of 20 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

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.