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

[PATCH v5 1/2] repo: add level of concern reporting

From
MCMark C. Chu-Carroll <markchucarroll@fastmail.com>
Date
Nov 2, 2026, 16:32 UTC
Message-ID
<20261102-b4-repo-structure-concern-v5-1-2d8f37b70b75@fastmail.com>
In-Reply-To
<20261102-b4-repo-structure-concern-v5-0-2d8f37b70b75@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?

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 |  16 +-
 builtin/repo.c              | 419 ++++++++++++++++++++++++++++++++++++++++----
 t/t1901-repo-structure.sh   |   1 -
 3 files changed, 397 insertions(+), 39 deletions(-)
diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc
index ed7d80c690..f186b27d87 100644
--- a/Documentation/git-repo.adoc
+++ b/Documentation/git-repo.adoc
@@ -10,7 +10,7 @@ SYNOPSIS
 [synopsis]
 git repo info [--format=(lines|nul) | -z] [--all | <key>...]
 git repo info --keys [--format=(lines|nul) | -z]
-git repo structure [--format=(table|lines|nul) | -z]
+git repo structure [--format=(table|lines|nul) | -z] [--[no-]concern]
 
 DESCRIPTION
 -----------
@@ -56,7 +56,7 @@ supported:
 `nul`:::
 	Similar to `lines`, but using a _NUL_ character after each value.
 
-`structure [--format=(table|lines|nul) | -z]`::
+`structure [--format=(table|lines|nul) | -z] [--[no-]concern]`::
 	Retrieve statistics about the current repository structure. The
 	following kinds of information are reported:
 +
@@ -88,6 +88,18 @@ supported:
 	quoted.
 +
 `-z` is an alias for `--format=nul`.
++
+To make it easier to know when one of the values is something that
+users should be concerned about, adding '--concern' will provide a
+level of concern to be added to the output. In 'table' mode, this will
+be a table column indicating the level of concern as a number of stars;
+in `lines` or `nul` mode, a `concern_level` line will be added for each
+statistic with a numeric value.
++
+The level of concern for each statistic is computed using the log_2 of the
+statistic value. The value is considered concerning if it exceeds a heuristically
+chosen threshold value; the degree of concern is the difference between the
+log_2 of the value, and the log_2 of the corresponding threshold.
 
 INFO KEYS
 ---------
diff --git a/builtin/repo.c b/builtin/repo.c
index 84e012f83f..7a50605779 100644
--- a/builtin/repo.c
+++ b/builtin/repo.c
@@ -1,7 +1,9 @@
 #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"
@@ -21,13 +23,16 @@
 #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 \
-	"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,
@@ -348,8 +353,93 @@ struct stats_table {
 	int name_col_width;
 	int value_col_width;
 	int unit_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
+ * of concern, which will appear as 1 star. Then we scale exponentially:
+ * each time that value doubles, we add another star.
+ *
+ * The threshold values for concern are expressed as log_2 values of the size
+ * at which the statistic becomes concerning. The default values for the
+ * threshold were selected based on observed values from a collection of
+ * representative-ish repositories of varying size, by finding the mean log_2
+ * value and standard deviation (sigma) for each statistic over the collection.
+ * The threshold for each metric is then set at mean(log_2) + sigma.
+ */
+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"
+#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"
+
+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 },
+	{ .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 }
+};
+
+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))
+			return thresholds[i].threshold;
+	}
+	return -1;
+}
+
+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;
+	}
+}
+
 /*
  * Holds column data that gets stored for each row.
  */
@@ -358,6 +448,7 @@ struct stats_table_entry {
 	const char *unit;
 	size_t index;
 	struct object_id *oid;
+	size_t concern;
 };
 
 static void stats_table_vaddf(struct stats_table *table,
@@ -391,6 +482,9 @@ static void stats_table_vaddf(struct stats_table *table,
 		if (value_width > table->value_col_width)
 			table->value_col_width = value_width;
 	}
+	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)
@@ -409,14 +503,21 @@ 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, ...)
 {
 	struct stats_table_entry *entry;
 	va_list ap;
 
 	CALLOC_ARRAY(entry, 1);
 	humanise_count(value, &entry->value, &entry->unit);
+	entry->concern = concern;
 
 	va_start(ap, format);
 	stats_table_vaddf(table, entry, format, ap);
@@ -425,13 +526,14 @@ static void stats_table_count_addf(struct stats_table *table, size_t value,
 
 static void stats_table_object_count_addf(struct stats_table *table,
 					  struct object_id *oid, size_t value,
-					  const char *format, ...)
+					  size_t concern, const char *format, ...)
 {
 	struct stats_table_entry *entry;
 	va_list ap;
 
 	CALLOC_ARRAY(entry, 1);
 	humanise_count(value, &entry->value, &entry->unit);
+	entry->concern = concern;
 
 	/*
 	 * A NULL OID should not have a table annotation.
@@ -445,6 +547,7 @@ static void stats_table_object_count_addf(struct stats_table *table,
 }
 
 static void stats_table_size_addf(struct stats_table *table, size_t value,
+				  size_t concern,
 				  const char *format, ...)
 {
 	struct stats_table_entry *entry;
@@ -452,6 +555,7 @@ static void stats_table_size_addf(struct stats_table *table, size_t value,
 
 	CALLOC_ARRAY(entry, 1);
 	humanise_bytes(value, &entry->value, &entry->unit, HUMANISE_COMPACT);
+	entry->concern = concern;
 
 	va_start(ap, format);
 	stats_table_vaddf(table, entry, format, ap);
@@ -460,6 +564,7 @@ static void stats_table_size_addf(struct stats_table *table, size_t value,
 
 static void stats_table_object_size_addf(struct stats_table *table,
 					 struct object_id *oid, size_t value,
+					 size_t concern,
 					 const char *format, ...)
 {
 	struct stats_table_entry *entry;
@@ -467,6 +572,7 @@ static void stats_table_object_size_addf(struct stats_table *table,
 
 	CALLOC_ARRAY(entry, 1);
 	humanise_bytes(value, &entry->value, &entry->unit, HUMANISE_COMPACT);
+	entry->concern = concern;
 
 	/*
 	 * A NULL OID should not have a table annotation.
@@ -490,7 +596,8 @@ 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;
@@ -501,47 +608,113 @@ static void stats_table_setup_structure(struct stats_table *table,
 
 	ref_total = get_total_reference_count(refs);
 	stats_table_addf(table, "* %s", _("References"));
-	stats_table_count_addf(table, ref_total, "  * %s", _("Count"));
-	stats_table_count_addf(table, refs->branches, "    * %s", _("Branches"));
-	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, CONCERN_STAT_VALUE_NONE,
+			       "  * %s", _("Count"));
+	stats_table_count_addf(table, refs->branches, CONCERN_STAT_VALUE_NONE,
+			       "    * %s", _("Branches"));
+	stats_table_count_addf(table, refs->tags,
+			       CONCERN_STAT_VALUE_NONE,
+			       "    * %s", _("Tags"));
+	stats_table_count_addf(table, refs->remotes, CONCERN_STAT_VALUE_NONE,
+			       "    * %s", _("Remotes"));
+	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, 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,
+				       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,
+				       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,
+			               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,
+				       show_concern),
 			       "    * %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, 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,
+				      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,
+				      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,
+				      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,
+				      show_concern),
 			      "    * %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, 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,
+				      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,
+				      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,
+				      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,
+				      show_concern),
 			      "    * %s", _("Tags"));
 
 	stats_table_addf(table, "");
@@ -550,35 +723,59 @@ static void stats_table_setup_structure(struct stats_table *table,
 	stats_table_object_size_addf(table,
 				     &objects->largest.commit_size.oid,
 				     objects->largest.commit_size.value,
+				     repo_structure_concern_level(
+					     CONCERN_STAT_COMMIT_LARGEST,
+					     objects->largest.commit_size.value,
+					     repo_structure_concern_thresholds,
+					     show_concern),
 				     "    * %s", _("Maximum size"));
 	stats_table_object_count_addf(table,
 				      &objects->largest.parent_count.oid,
 				      objects->largest.parent_count.value,
+				      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_thresholds,
+					     show_concern),
 				     "    * %s", _("Maximum size"));
+
 	stats_table_object_count_addf(table,
-				      &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,
+					     repo_structure_concern_thresholds,
+					     show_concern),
 				     "    * %s", _("Maximum size"));
 	stats_table_addf(table, "  * %s", _("Tags"));
 	stats_table_object_size_addf(table,
 				     &objects->largest.tag_size.oid,
 				     objects->largest.tag_size.value,
+				     repo_structure_concern_level(
+					     CONCERN_STAT_TAG_LARGEST,
+					     objects->largest.tag_size.value,
+					     repo_structure_concern_thresholds,
+					     show_concern),
 				     "    * %s", _("Maximum size"));
 }
 
 #define INDEX_WIDTH 4
 
-static void stats_table_print_structure(const struct stats_table *table)
+static void stats_table_print_structure(const struct stats_table *table,
+					bool show_concern)
 {
 	const char *name_col_title = _("Repository structure");
 	const char *value_col_title = _("Value");
@@ -587,14 +784,17 @@ 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;
+	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;
 
 	if (title_name_width > name_col_width)
 		name_col_width = title_name_width;
 	if (title_value_width > value_col_width + unit_col_width + 1)
 		value_col_width = title_value_width - unit_col_width;
-
 	strbuf_addstr(&buf, "| ");
 	strbuf_utf8_align(&buf, ALIGN_LEFT, name_col_width + INDEX_WIDTH,
 			  name_col_title);
@@ -602,6 +802,12 @@ static void stats_table_print_structure(const struct stats_table *table)
 	strbuf_utf8_align(&buf, ALIGN_LEFT,
 			  value_col_width + unit_col_width + 1, value_col_title);
 	strbuf_addstr(&buf, " |");
+	if (show_concern) {
+		strbuf_addch(&buf, ' ');
+		strbuf_utf8_align(&buf, ALIGN_LEFT, concern_col_width,
+			  concern_col_title);
+		strbuf_addstr(&buf, " |");
+	}
 	printf("%s\n", buf.buf);
 
 	printf("| ");
@@ -610,17 +816,28 @@ static void stats_table_print_structure(const struct stats_table *table)
 	printf(" | ");
 	for (int i = 0; i < value_col_width + unit_col_width + 1; i++)
 		putchar('-');
-	printf(" |\n");
+	printf(" |");
+	if (show_concern) {
+		putchar(' ');
+		for (size_t i = 0; i < concern_col_width; i++) {
+			putchar('-');
+		}
+		printf(" |");
+	}
+	putchar('\n');
 
 	for_each_string_list_item(item, &table->rows) {
 		struct stats_table_entry *entry = item->util;
 		const char *value = "";
 		const char *unit = "";
+		strbuf_reset(&concern_str);
 
 		if (entry) {
 			value = entry->value;
 			if (entry->unit)
 				unit = entry->unit;
+			if (entry->concern > 0)
+				strbuf_addchars(&concern_str, '*', entry->concern);
 		}
 
 		strbuf_reset(&buf);
@@ -638,6 +855,11 @@ static void stats_table_print_structure(const struct stats_table *table)
 		strbuf_addch(&buf, ' ');
 		strbuf_utf8_align(&buf, ALIGN_LEFT, unit_col_width, unit);
 		strbuf_addstr(&buf, " |");
+		if (show_concern) {
+			strbuf_addch(&buf, ' ');
+			strbuf_utf8_align(&buf, ALIGN_LEFT, concern_col_width, concern_str.buf);
+			strbuf_addstr(&buf, " |");
+		}
 		printf("%s\n", buf.buf);
 	}
 
@@ -646,7 +868,7 @@ 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);
 }
 
@@ -681,7 +903,8 @@ static void print_object_data(const char *key, char key_delim,
 }
 
 static void structure_keyvalue_print(struct repo_structure *stats,
-				     char key_delim, char value_delim)
+				     char key_delim, char value_delim,
+				     bool show_concern)
 {
 	print_keyvalue("references.branches.count", key_delim,
 		       stats->refs.branches, value_delim);
@@ -691,48 +914,170 @@ static void structure_keyvalue_print(struct repo_structure *stats,
 		       stats->refs.remotes, value_delim);
 	print_keyvalue("references.others.count", key_delim,
 		       stats->refs.others, value_delim);
-
 	print_keyvalue("objects.commits.count", key_delim,
 		       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,
+				       repo_structure_concern_thresholds,
+				       show_concern),
+			       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,
+				       repo_structure_concern_thresholds,
+				       show_concern),
+			       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,
+				       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,
+				       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);
+	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,
+				       repo_structure_concern_thresholds,
+				       show_concern),
+			       value_delim);
 	print_keyvalue("objects.trees.inflated_size", key_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,
+					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)
+		print_keyvalue("objects.blobs.inflated_size.concern_level", key_delim,
+			       repo_structure_concern_level(
+				       CONCERN_STAT_BLOB_INFLATED_SIZE,
+				       stats->objects.inflated_sizes.blobs,
+				       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,
+				       show_concern),
+			       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,
+				       repo_structure_concern_thresholds,
+				       show_concern),
+			       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,
+				       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,
+				       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,
+				       show_concern),
+			      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,
+				       repo_structure_concern_thresholds,
+				       show_concern),
+			       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,
+				       repo_structure_concern_thresholds,
+				       show_concern),
+			       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,
+				       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,
+				       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);
 }
 
@@ -935,6 +1280,8 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
 	struct repo_structure stats = { 0 };
 	struct rev_info revs;
 	int show_progress = -1;
+	int show_concern = 0;
+
 	struct option options[] = {
 		OPT_CALLBACK_F(0, "format", &format, N_("format"),
 			       N_("output format"),
@@ -944,13 +1291,13 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
 			       PARSE_OPT_NONEG | PARSE_OPT_NOARG,
 			       parse_format_cb),
 		OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
+		OPT_BOOL(0, "concern", &show_concern, N_("show level of concern")),
 		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)
@@ -961,14 +1308,14 @@ 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_print_structure(&table);
+		stats_table_setup_structure(&table, &stats, show_concern);
+		stats_table_print_structure(&table, show_concern);
 		break;
 	case FORMAT_NEWLINE_TERMINATED:
-		structure_keyvalue_print(&stats, '=', '\n');
+		structure_keyvalue_print(&stats, '=', '\n', show_concern);
 		break;
 	case FORMAT_NUL_TERMINATED:
-		structure_keyvalue_print(&stats, '\n', '\0');
+		structure_keyvalue_print(&stats, '\n', '\0', show_concern);
 		break;
 	default:
 		BUG("invalid output format");
diff --git a/t/t1901-repo-structure.sh b/t/t1901-repo-structure.sh
index 02cc2b594a..9eafff0a36 100755
--- a/t/t1901-repo-structure.sh
+++ b/t/t1901-repo-structure.sh
@@ -65,7 +65,6 @@ test_expect_success 'empty repository' '
 		|   * Tags                  |        |
 		|     * Maximum size        |    0 B |
 		EOF
-
 		git repo structure >out 2>err &&
 
 		test_cmp expect out &&
-- 
2.53.0
Previous: Mark C. Chu-CarrollNext: Mark C. Chu-Carroll
Message 22 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.