{"thread":{"id":"66621","subject":"[PATCH 0/3] repo: add degree-of-concern reporting to structure","startedAt":"2026-10-21T13:55:04Z","lastAt":"2026-11-02T16:32:44Z","messageCount":23,"participants":["Mark C. Chu-Carroll","Patrick Steinhardt","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":3},"messages":[{"id":"555734","messageId":"20261021-b4-repo-structure-concern-v1-0-e3010552292c@fastmail.com","threadId":"66621","inReplyTo":null,"subject":"[PATCH 0/3] repo: add degree-of-concern reporting to structure","fromName":"Mark C. Chu-Carroll","fromEmail":"markchucarroll@fastmail.com","sentAt":"2026-10-21T13:55:04Z","receivedAt":"2026-10-21T13:55:04Z","isPatch":true,"sender":{"key":"markchucarroll@fastmail.com","avatar":null},"body":"\"git repo structure\" computes a set of statistics that are useful for\ndiagnosing performance issues in a git repository. But for a typical\nuser who hasn't encountered any issues yet, it can be difficult to\ninterpret the data - when should a value be considered concerning?\n\nThis patchset introduces a feature to \"git repo structure\" that reports\non how concerning certain measures of storage structure  are for a\nrepository.  In table mode, concern is displayed as a column containing\na number of stars indicating the degree of concern; in line or nul mode,\nconcerns are displayed as additional lines with a numeric value.\n\nTo select defaults for the thresholds where a value becomes concern,\na (hopefully) representative selection of repositories were analyzed.\nBased on empirical exploration of the data, the measurement that seemed\nclosest to accurately assessing when a repository's structure becomes\nconcerning was based on the base 2 logarithm of the value. Across all of\nthe sample repositories, for all of the measurements, the point at which\nthings become problematic was when a base 2 logarithm of a value was\nmore than 1 standard deviation from the mean of the log_2 values in\nthe sample set.  Based on this, the concern threshold was set for each\nof the measures.\n\nWhile this seems like a reasonable approach for setting default\nthresholds, it's obviously still very ad-hoc. It's likely that over\ntime, the default values will evolve; and in the short term, projects\nmay want to set their own thresholds. So this patchset includes support\nfor configuring the thresholds on a per-reposiry basis.\n\nOverview of the change:\n\n* The first commit adds the command-line option \"--concern\", and creates the basic implementation of concern reporting.\n* The next commit adds configurability for the concern thresholds.\n* The final commit update the documentation for the command and\n  configuration options.\n\nSigned-off-by: Mark C. Chu-Carroll <markchucarroll@fastmail.com>\n---\nMark C. Chu-Carroll (3):\n      repo: add level of concern reporting\n      repo: make concern thresholds configurable\n      repo: add documentation for level-of-concern.\n\n Documentation/config/repo.adoc |  60 ++++++++\n Documentation/git-repo.adoc    |  27 +++-\n builtin/repo.c                 | 335 ++++++++++++++++++++++++++++++++++++++---\n t/t1901-repo-structure.sh      | 134 +++++++++++++++++\n 4 files changed, 535 insertions(+), 21 deletions(-)\n\n\n---\nbase-commit: 3bc0341126508f78f5869cbfc0005e987efdf0c7\nchange-id: 20261019-b4-repo-structure-concern-f1d6f6326095\n\n\n"},{"id":"555735","messageId":"20261021-b4-repo-structure-concern-v1-1-e3010552292c@fastmail.com","threadId":"66621","inReplyTo":"20261021-b4-repo-structure-concern-v1-0-e3010552292c@fastmail.com","subject":"[PATCH 1/3] repo: add level of concern reporting","fromName":"Mark C. Chu-Carroll","fromEmail":"markchucarroll@fastmail.com","sentAt":"2026-10-21T13:55:05Z","receivedAt":"2026-10-21T13:55:05Z","isPatch":true,"sender":{"key":"markchucarroll@fastmail.com","avatar":null},"body":"\"git repo structure\" computes a set of statistics\nthat are useful for diagnosing performance issues in\na git repository. But for a typical user who hasn't\nencountered any issues yet, it can be difficult to\ninterpret the data - when should a value be considered\nconcerning?\n\nAdd an option to \"git repo structure\" that adds a \"degree\nof concern\" column to the structure report, which indicates\nwhen a stat values has reached a point where it might be\nconcerning, and indicate how severe the concern should be.\n---\n builtin/repo.c            | 329 +++++++++++++++++++++++++++++++++++++++++++---\n sideband.c                |   2 +-\n t/t1901-repo-structure.sh | 125 ++++++++----------\n 3 files changed, 366 insertions(+), 90 deletions(-)\n\ndiff --git a/builtin/repo.c b/builtin/repo.c\nindex 84e012f83f..a450497d4e 100644\n--- a/builtin/repo.c\n+++ b/builtin/repo.c\n@@ -27,7 +27,7 @@\n \t\"git repo info --keys [--format=(lines|nul) | -z]\"\n \n #define REPO_STRUCTURE_USAGE \\\n-\t\"git repo structure [--format=(table|lines|nul) | -z]\"\n+\t\"git repo structure [--format=(table|lines|nul) | -z] [--[no-]concern]\"\n \n static const char *const repo_usage[] = {\n \tREPO_INFO_USAGE,\n@@ -348,8 +348,118 @@ struct stats_table {\n \tint name_col_width;\n \tint value_col_width;\n \tint unit_col_width;\n+\tint concern_col_width;\n };\n \n+/*\n+ * For computing level of concern about a stat, we need to have some\n+ * baseline values. For each of these, we'define a minimum level\n+ * of concern, which will appear as 1 star. Then we scale exponentially:\n+ * each time that value doubles, we add another star.\n+ *\n+ * The threshold values for concern are expressed as log_2 values of the size\n+ * at which the statistic becomes concerning. The default values for the\n+ * threshold were selected based on observed values from a collection of\n+ * representative-ish repositories of varying size, by finding the mean log_2\n+ * value and standard deviation (sigma) for each statistic over the collection.\n+ * The threshold for each metric is then set at mean(log_2) + sigma.\n+ */\n+struct concern_stat_threshold {\n+\tconst char *name;\n+\tsize_t threshold;\n+};\n+\n+enum concern_stat {\n+\tCONCERN_STAT_COMMIT_COUNT,\n+\tCONCERN_STAT_TREE_COUNT,\n+\tCONCERN_STAT_BLOB_COUNT,\n+\tCONCERN_STAT_TAG_COUNT,\n+\tCONCERN_STAT_COMMIT_SIZE,\n+\tCONCERN_STAT_TREE_SIZE,\n+\tCONCERN_STAT_BLOB_SIZE,\n+\tCONCERN_STAT_TAG_SIZE,\n+\tCONCERN_STAT_COMMIT_LARGEST,\n+\tCONCERN_STAT_TREE_LARGEST,\n+\tCONCERN_STAT_BLOB_LARGEST,\n+\tCONCERN_STAT_TAG_LARGEST,\n+\tCONCERN_STAT_COMMIT_INFLATED_SIZE,\n+\tCONCERN_STAT_TREE_INFLATED_SIZE,\n+\tCONCERN_STAT_BLOB_INFLATED_SIZE,\n+\tCONCERN_STAT_TAG_INFLATED_SIZE,\n+};\n+\n+struct concern_stat_threshold repo_structure_concern_thresholds[] = {\n+\t{ .name = \"commit_count\", .threshold = 19 },\n+\t{.name = \"tree_count\", .threshold = 21 },\n+\t{ .name = \"blob_count\", .threshold = 22 },\n+\t{ .name = \"tag_count\", .threshold = 15 },\n+\t{ .name = \"commit_size\", .threshold = 28 },\n+\t{ .name = \"tree_size\", .threshold = 27 },\n+\t{ .name = \"blob_size\", .threshold = 36 },\n+\t{ .name = \"tags_size\", .threshold = 18 },\n+\t{ .name = \"commit_largest\", .threshold = 18  },\n+\t{ .name = \"tree_largest\", .threshold = 18 },\n+\t{ .name = \"blob_largest\", .threshold = 26 },\n+\t{ .name = \"tag_largest\", .threshold =  15 },\n+\t{ .name = \"commit_inflated_size\", .threshold = 28 },\n+\t{ .name = \"tree_inflated_size\", .threshold = 32 },\n+\t{ .name = \"blob_inflated_size\", .threshold = 34 },\n+\t{ .name = \"tag_inflated_size\", .threshold = 25}\n+};\n+\n+/*\n+ * Concern thresholds aren't universal. We try to set reasonable default\n+ * values, but projects may want to set these thresholds differently. To\n+ * support that, they can set the thresholds for their project in the\n+ * project's git config, in the section \"repo.structure.concern\".\n+  */\n+static int config_concern_thresholds(const char *prefixed_var,\n+\t\t\t\t     const char *str_val,\n+\t\t\t\t     const struct config_context *ctx,\n+\t\t\t\t     void *cb UNUSED) {\n+\tconst char *config_prefix = \"repo.structure.concern.\";\n+\tif (!strncmp(config_prefix, prefixed_var, strlen(config_prefix))) {\n+\t\tconst char *concern_name = &prefixed_var[strlen(config_prefix)];\n+\t\tfor (int i = 0; concern_stat_thresholds[i].name != 0; i++) {\n+\t\t\tif (!strcmp(concern_stat_thresholds[i].name, concern_name)) {\n+\t\t\t\tint value = git_config_int(prefixed_var, str_val, ctx->kvi);\n+\t\t\t\tif (value < 0)\n+\t\t\t\t\treturn -1;\n+\t\t\t\tconcern_stat_thresholds[i].threshold = value;\n+\t\t\t\treturn 0;\n+\t\t\t}\n+\t\t}\n+\t\twarning(_(\"Invalid concern threshold name: '%s'\"), concern_name);\n+\t\treturn 0;\n+\t}\n+\treturn 0;\n+}\n+\n+static size_t approx_log2(long value) {\n+\tint position = 0;\n+\tif (value <= 0) {\n+\t\treturn 0;\n+\t}\n+\twhile (value > 1) {\n+\t\tposition++;\n+\t\tvalue >>= 1;\n+\t}\n+\treturn position;\n+}\n+\n+static size_t repo_structure_concern_level(enum concern_stat stat_id,\n+\t\t\t\t\t   size_t value) {\n+\tsize_t log_value;\n+\tif (stat_id >= ARRAY_SIZE(concern_stat_thresholds))\n+\t\tBUG(\"unexpected repository stat type\");\n+\tlog_value = approx_log2(value);\n+\tif (log_value > concern_stat_thresholds[stat_id].threshold)\n+\t\treturn log_value -\n+\t\t\tconcern_stat_thresholds[stat_id].threshold;\n+\telse\n+\t\treturn 0;\n+}\n+\n /*\n  * Holds column data that gets stored for each row.\n  */\n@@ -358,6 +468,7 @@ struct stats_table_entry {\n \tconst char *unit;\n \tsize_t index;\n \tstruct object_id *oid;\n+\tsize_t concern;\n };\n \n static void stats_table_vaddf(struct stats_table *table,\n@@ -391,6 +502,8 @@ static void stats_table_vaddf(struct stats_table *table,\n \t\tif (value_width > table->value_col_width)\n \t\t\ttable->value_col_width = value_width;\n \t}\n+\tif ((int)entry->concern > table->concern_col_width)\n+\t\ttable->concern_col_width = entry->concern;\n \tif (entry->unit) {\n \t\tint unit_width = utf8_strwidth(entry->unit);\n \t\tif (unit_width > table->unit_col_width)\n@@ -410,13 +523,14 @@ static void stats_table_addf(struct stats_table *table, const char *format, ...)\n }\n \n static void stats_table_count_addf(struct stats_table *table, size_t value,\n-\t\t\t\t   const char *format, ...)\n+\t\t\t\t   size_t concern, const char *format, ...)\n {\n \tstruct stats_table_entry *entry;\n \tva_list ap;\n \n \tCALLOC_ARRAY(entry, 1);\n \thumanise_count(value, &entry->value, &entry->unit);\n+\tentry->concern = concern;\n \n \tva_start(ap, format);\n \tstats_table_vaddf(table, entry, format, ap);\n@@ -425,13 +539,14 @@ static void stats_table_count_addf(struct stats_table *table, size_t value,\n \n static void stats_table_object_count_addf(struct stats_table *table,\n \t\t\t\t\t  struct object_id *oid, size_t value,\n-\t\t\t\t\t  const char *format, ...)\n+\t\t\t\t\t  size_t concern, const char *format, ...)\n {\n \tstruct stats_table_entry *entry;\n \tva_list ap;\n \n \tCALLOC_ARRAY(entry, 1);\n \thumanise_count(value, &entry->value, &entry->unit);\n+\tentry->concern = concern;\n \n \t/*\n \t * A NULL OID should not have a table annotation.\n@@ -445,6 +560,7 @@ static void stats_table_object_count_addf(struct stats_table *table,\n }\n \n static void stats_table_size_addf(struct stats_table *table, size_t value,\n+\t\t\t\t  size_t concern,\n \t\t\t\t  const char *format, ...)\n {\n \tstruct stats_table_entry *entry;\n@@ -452,6 +568,7 @@ static void stats_table_size_addf(struct stats_table *table, size_t value,\n \n \tCALLOC_ARRAY(entry, 1);\n \thumanise_bytes(value, &entry->value, &entry->unit, HUMANISE_COMPACT);\n+\tentry->concern = concern;\n \n \tva_start(ap, format);\n \tstats_table_vaddf(table, entry, format, ap);\n@@ -460,6 +577,7 @@ static void stats_table_size_addf(struct stats_table *table, size_t value,\n \n static void stats_table_object_size_addf(struct stats_table *table,\n \t\t\t\t\t struct object_id *oid, size_t value,\n+\t\t\t\t\t size_t concern,\n \t\t\t\t\t const char *format, ...)\n {\n \tstruct stats_table_entry *entry;\n@@ -467,6 +585,7 @@ static void stats_table_object_size_addf(struct stats_table *table,\n \n \tCALLOC_ARRAY(entry, 1);\n \thumanise_bytes(value, &entry->value, &entry->unit, HUMANISE_COMPACT);\n+\tentry->concern = concern;\n \n \t/*\n \t * A NULL OID should not have a table annotation.\n@@ -501,47 +620,92 @@ static void stats_table_setup_structure(struct stats_table *table,\n \n \tref_total = get_total_reference_count(refs);\n \tstats_table_addf(table, \"* %s\", _(\"References\"));\n-\tstats_table_count_addf(table, ref_total, \"  * %s\", _(\"Count\"));\n-\tstats_table_count_addf(table, refs->branches, \"    * %s\", _(\"Branches\"));\n-\tstats_table_count_addf(table, refs->tags, \"    * %s\", _(\"Tags\"));\n-\tstats_table_count_addf(table, refs->remotes, \"    * %s\", _(\"Remotes\"));\n-\tstats_table_count_addf(table, refs->others, \"    * %s\", _(\"Others\"));\n+\tstats_table_count_addf(table, ref_total, 0,\n+\t\t\t       \"  * %s\", _(\"Count\"));\n+\tstats_table_count_addf(table, refs->branches, 0,\n+\t\t\t       \"    * %s\", _(\"Branches\"));\n+\tstats_table_count_addf(table, refs->tags,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_TAG_COUNT,\n+\t\t\t\t       refs->tags),\n+\t\t\t       \"    * %s\", _(\"Tags\"));\n+\tstats_table_count_addf(table, refs->remotes, 0,\n+\t\t\t       \"    * %s\", _(\"Remotes\"));\n+\tstats_table_count_addf(table, refs->others, 0,\n+\t\t\t       \"    * %s\", _(\"Others\"));\n \n \tobject_count_total = get_total_object_values(&objects->type_counts);\n \tstats_table_addf(table, \"\");\n \tstats_table_addf(table, \"* %s\", _(\"Reachable objects\"));\n-\tstats_table_count_addf(table, object_count_total, \"  * %s\", _(\"Count\"));\n+\tstats_table_count_addf(table, object_count_total, 0,\n+\t\t\t       \"  * %s\", _(\"Count\"));\n \tstats_table_count_addf(table, objects->type_counts.commits,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_COMMIT_COUNT,\n+\t\t\t\t       objects->type_counts.commits),\n \t\t\t       \"    * %s\", _(\"Commits\"));\n \tstats_table_count_addf(table, objects->type_counts.trees,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_TREE_COUNT,\n+\t\t\t\t       objects->type_counts.trees),\n \t\t\t       \"    * %s\", _(\"Trees\"));\n \tstats_table_count_addf(table, objects->type_counts.blobs,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_BLOB_COUNT,\n+\t\t\t\t       objects->type_counts.blobs),\n \t\t\t       \"    * %s\", _(\"Blobs\"));\n \tstats_table_count_addf(table, objects->type_counts.tags,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_TAG_COUNT,\n+\t\t\t\t       objects->type_counts.tags),\n \t\t\t       \"    * %s\", _(\"Tags\"));\n \n \tinflated_object_total = get_total_object_values(&objects->inflated_sizes);\n-\tstats_table_size_addf(table, inflated_object_total,\n+\tstats_table_size_addf(table, inflated_object_total, 0,\n \t\t\t      \"  * %s\", _(\"Inflated size\"));\n \tstats_table_size_addf(table, objects->inflated_sizes.commits,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_COMMIT_INFLATED_SIZE,\n+\t\t\t\t      objects->inflated_sizes.commits),\n \t\t\t      \"    * %s\", _(\"Commits\"));\n \tstats_table_size_addf(table, objects->inflated_sizes.trees,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_TREE_INFLATED_SIZE,\n+\t\t\t\t      objects->inflated_sizes.trees),\n \t\t\t      \"    * %s\", _(\"Trees\"));\n \tstats_table_size_addf(table, objects->inflated_sizes.blobs,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_BLOB_INFLATED_SIZE,\n+\t\t\t\t      objects->inflated_sizes.blobs),\n \t\t\t      \"    * %s\", _(\"Blobs\"));\n \tstats_table_size_addf(table, objects->inflated_sizes.tags,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_TAG_INFLATED_SIZE,\n+\t\t\t\t      objects->inflated_sizes.tags),\n \t\t\t      \"    * %s\", _(\"Tags\"));\n \n \tdisk_object_total = get_total_object_values(&objects->disk_sizes);\n-\tstats_table_size_addf(table, disk_object_total,\n+\tstats_table_size_addf(table, disk_object_total, 0,\n \t\t\t      \"  * %s\", _(\"Disk size\"));\n \tstats_table_size_addf(table, objects->disk_sizes.commits,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_COMMIT_SIZE,\n+\t\t\t\t      objects->disk_sizes.commits),\n \t\t\t      \"    * %s\", _(\"Commits\"));\n \tstats_table_size_addf(table, objects->disk_sizes.trees,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_TREE_SIZE,\n+\t\t\t\t      objects->disk_sizes.trees),\n \t\t\t      \"    * %s\", _(\"Trees\"));\n \tstats_table_size_addf(table, objects->disk_sizes.blobs,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_BLOB_SIZE,\n+\t\t\t\t      objects->disk_sizes.blobs),\n \t\t\t      \"    * %s\", _(\"Blobs\"));\n \tstats_table_size_addf(table, objects->disk_sizes.tags,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_TAG_SIZE,\n+\t\t\t\t      objects->disk_sizes.tags),\n \t\t\t      \"    * %s\", _(\"Tags\"));\n \n \tstats_table_addf(table, \"\");\n@@ -550,51 +714,71 @@ static void stats_table_setup_structure(struct stats_table *table,\n \tstats_table_object_size_addf(table,\n \t\t\t\t     &objects->largest.commit_size.oid,\n \t\t\t\t     objects->largest.commit_size.value,\n+\t\t\t\t     repo_structure_concern_level(\n+\t\t\t\t\t     CONCERN_STAT_COMMIT_LARGEST,\n+\t\t\t\t\t     objects->largest.commit_size.value),\n \t\t\t\t     \"    * %s\", _(\"Maximum size\"));\n \tstats_table_object_count_addf(table,\n \t\t\t\t      &objects->largest.parent_count.oid,\n \t\t\t\t      objects->largest.parent_count.value,\n+\t\t\t\t      0,\n \t\t\t\t      \"    * %s\", _(\"Maximum parents\"));\n \tstats_table_addf(table, \"  * %s\", _(\"Trees\"));\n \tstats_table_object_size_addf(table,\n \t\t\t\t     &objects->largest.tree_size.oid,\n \t\t\t\t     objects->largest.tree_size.value,\n+\t\t\t\t      repo_structure_concern_level(\n+\t\t\t\t\t      CONCERN_STAT_TREE_LARGEST,\n+\t\t\t\t\t      objects->largest.tree_size.value),\n \t\t\t\t     \"    * %s\", _(\"Maximum size\"));\n \tstats_table_object_count_addf(table,\n \t\t\t\t      &objects->largest.tree_entries.oid,\n \t\t\t\t      objects->largest.tree_entries.value,\n+\t\t\t\t      0,\n \t\t\t\t      \"    * %s\", _(\"Maximum entries\"));\n \tstats_table_addf(table, \"  * %s\", _(\"Blobs\"));\n \tstats_table_object_size_addf(table,\n \t\t\t\t     &objects->largest.blob_size.oid,\n \t\t\t\t     objects->largest.blob_size.value,\n+\t\t\t\t     repo_structure_concern_level(\n+\t\t\t\t\t     CONCERN_STAT_BLOB_LARGEST,\n+\t\t\t\t\t     objects->largest.blob_size.value),\n \t\t\t\t     \"    * %s\", _(\"Maximum size\"));\n \tstats_table_addf(table, \"  * %s\", _(\"Tags\"));\n \tstats_table_object_size_addf(table,\n \t\t\t\t     &objects->largest.tag_size.oid,\n \t\t\t\t     objects->largest.tag_size.value,\n+\t\t\t\t     repo_structure_concern_level(\n+\t\t\t\t\t     CONCERN_STAT_TAG_LARGEST,\n+\t\t\t\t\t     objects->largest.tag_size.value),\n \t\t\t\t     \"    * %s\", _(\"Maximum size\"));\n }\n \n #define INDEX_WIDTH 4\n \n-static void stats_table_print_structure(const struct stats_table *table)\n+static void stats_table_print_structure(const struct stats_table *table,\n+\t\t\t\t\tbool show_concern)\n {\n \tconst char *name_col_title = _(\"Repository structure\");\n \tconst char *value_col_title = _(\"Value\");\n+\tconst char *concern_col_title = _(\"Concern\");\n \tint title_name_width = utf8_strwidth(name_col_title);\n \tint title_value_width = utf8_strwidth(value_col_title);\n+\tint title_concern_width = utf8_strwidth(concern_col_title);\n \tint name_col_width = table->name_col_width;\n \tint value_col_width = table->value_col_width;\n \tint unit_col_width = table->unit_col_width;\n+\tint concern_col_width = table->concern_col_width;\n \tstruct string_list_item *item;\n \tstruct strbuf buf = STRBUF_INIT;\n+\tstruct strbuf concern_str = STRBUF_INIT;\n \n \tif (title_name_width > name_col_width)\n \t\tname_col_width = title_name_width;\n \tif (title_value_width > value_col_width + unit_col_width + 1)\n \t\tvalue_col_width = title_value_width - unit_col_width;\n-\n+\tif (show_concern && title_concern_width > concern_col_width)\n+\t\tconcern_col_width = title_concern_width;\n \tstrbuf_addstr(&buf, \"| \");\n \tstrbuf_utf8_align(&buf, ALIGN_LEFT, name_col_width + INDEX_WIDTH,\n \t\t\t  name_col_title);\n@@ -602,6 +786,12 @@ static void stats_table_print_structure(const struct stats_table *table)\n \tstrbuf_utf8_align(&buf, ALIGN_LEFT,\n \t\t\t  value_col_width + unit_col_width + 1, value_col_title);\n \tstrbuf_addstr(&buf, \" |\");\n+\tif (show_concern) {\n+\t\tstrbuf_addch(&buf, ' ');\n+\t\tstrbuf_utf8_align(&buf, ALIGN_LEFT, concern_col_width,\n+\t\t\t  concern_col_title);\n+\t\tstrbuf_addstr(&buf, \" |\");\n+\t}\n \tprintf(\"%s\\n\", buf.buf);\n \n \tprintf(\"| \");\n@@ -610,17 +800,28 @@ static void stats_table_print_structure(const struct stats_table *table)\n \tprintf(\" | \");\n \tfor (int i = 0; i < value_col_width + unit_col_width + 1; i++)\n \t\tputchar('-');\n-\tprintf(\" |\\n\");\n+\tprintf(\" |\");\n+\tif (show_concern) {\n+\t\tputchar(' ');\n+\t\tfor (int i = 0; i < concern_col_width; i++) {\n+\t\t\tputchar('-');\n+\t\t}\n+\t\tprintf(\" |\");\n+\t}\n+\tputchar('\\n');\n \n \tfor_each_string_list_item(item, &table->rows) {\n \t\tstruct stats_table_entry *entry = item->util;\n \t\tconst char *value = \"\";\n \t\tconst char *unit = \"\";\n+\t\tstrbuf_reset(&concern_str);\n \n \t\tif (entry) {\n \t\t\tvalue = entry->value;\n \t\t\tif (entry->unit)\n \t\t\t\tunit = entry->unit;\n+\t\t\tif (entry->concern > 0)\n+\t\t\t\tstrbuf_addchars(&concern_str, '*', entry->concern);\n \t\t}\n \n \t\tstrbuf_reset(&buf);\n@@ -638,6 +839,11 @@ static void stats_table_print_structure(const struct stats_table *table)\n \t\tstrbuf_addch(&buf, ' ');\n \t\tstrbuf_utf8_align(&buf, ALIGN_LEFT, unit_col_width, unit);\n \t\tstrbuf_addstr(&buf, \" |\");\n+\t\tif (show_concern) {\n+\t\t\tstrbuf_addch(&buf, ' ');\n+\t\t\tstrbuf_utf8_align(&buf, ALIGN_LEFT, concern_col_width, concern_str.buf);\n+\t\t\tstrbuf_addstr(&buf, \" |\");\n+\t\t}\n \t\tprintf(\"%s\\n\", buf.buf);\n \t}\n \n@@ -681,12 +887,15 @@ static void print_object_data(const char *key, char key_delim,\n }\n \n static void structure_keyvalue_print(struct repo_structure *stats,\n-\t\t\t\t     char key_delim, char value_delim)\n+\t\t\t\t     char key_delim, char value_delim,\n+\t\t\t\t     bool show_concern)\n {\n \tprint_keyvalue(\"references.branches.count\", key_delim,\n \t\t       stats->refs.branches, value_delim);\n+\n \tprint_keyvalue(\"references.tags.count\", key_delim,\n \t\t       stats->refs.tags, value_delim);\n+\n \tprint_keyvalue(\"references.remotes.count\", key_delim,\n \t\t       stats->refs.remotes, value_delim);\n \tprint_keyvalue(\"references.others.count\", key_delim,\n@@ -694,39 +903,120 @@ static void structure_keyvalue_print(struct repo_structure *stats,\n \n \tprint_keyvalue(\"objects.commits.count\", key_delim,\n \t\t       stats->objects.type_counts.commits, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.commits.count.concern_level\", key_delim,\n+\t\t\t      repo_structure_concern_level(CONCERN_STAT_COMMIT_COUNT,\n+\t\t\t\t\t\t\t   stats->objects.type_counts.commits),\n+\t\t\t      value_delim);\n \tprint_keyvalue(\"objects.trees.count\", key_delim,\n \t\t       stats->objects.type_counts.trees, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.trees.count.concern_level\", key_delim,\n+\t\t\t      repo_structure_concern_level(CONCERN_STAT_TREE_COUNT,\n+\t\t\t\t\t\t\t   stats->objects.type_counts.trees),\n+\t\t\t      value_delim);\n \tprint_keyvalue(\"objects.blobs.count\", key_delim,\n \t\t       stats->objects.type_counts.blobs, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.blobs.count.concern_level\", key_delim,\n+\t\t\t      repo_structure_concern_level(CONCERN_STAT_BLOB_COUNT,\n+\t\t\t\t\t\t\t   stats->objects.type_counts.blobs),\n+\t\t\t      value_delim);\n \tprint_keyvalue(\"objects.tags.count\", key_delim,\n \t\t       stats->objects.type_counts.tags, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.tags.count.concern_level\", key_delim,\n+\t\t\t      repo_structure_concern_level(CONCERN_STAT_TAG_COUNT,\n+\t\t\t\t\t\t\t   stats->objects.type_counts.tags),\n+\t\t\t      value_delim);\n \n \tprint_keyvalue(\"objects.commits.inflated_size\", key_delim,\n \t\t       stats->objects.inflated_sizes.commits, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.commits.inflated_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_COMMIT_INFLATED_SIZE,\n+\t\t\t\t\t\t\t    stats->objects.inflated_sizes.commits),\n+\t\t\t      value_delim);\n \tprint_keyvalue(\"objects.trees.inflated_size\", key_delim,\n \t\t       stats->objects.inflated_sizes.trees, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.trees.inflated_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_TREE_INFLATED_SIZE,\n+\t\t\t\t\t\t\t    stats->objects.inflated_sizes.trees),\n+\t\t\t      value_delim);\n+\n \tprint_keyvalue(\"objects.blobs.inflated_size\", key_delim,\n \t\t       stats->objects.inflated_sizes.blobs, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.blobs.inflated_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_BLOB_INFLATED_SIZE,\n+\t\t\t\t\t\t\t    stats->objects.inflated_sizes.blobs),\n+\t\t\t      value_delim);\n \tprint_keyvalue(\"objects.tags.inflated_size\", key_delim,\n \t\t       stats->objects.inflated_sizes.tags, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.tags.inflated_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_TAG_INFLATED_SIZE,\n+\t\t\t\t\t\t\t    stats->objects.inflated_sizes.tags),\n+\t\t\t      value_delim);\n \n \tprint_keyvalue(\"objects.commits.disk_size\", key_delim,\n \t\t       stats->objects.disk_sizes.commits, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.commits.disk_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_COMMIT_SIZE,\n+\t\t\t\t\t\t\t    stats->objects.disk_sizes.commits),\n+\t\t\t      value_delim);\n \tprint_keyvalue(\"objects.trees.disk_size\", key_delim,\n \t\t       stats->objects.disk_sizes.trees, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.trees.disk_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_TREE_SIZE,\n+\t\t\t\t\t\t\t    stats->objects.disk_sizes.trees),\n+\t\t\t      value_delim);\n \tprint_keyvalue(\"objects.blobs.disk_size\", key_delim,\n \t\t       stats->objects.disk_sizes.blobs, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.blobs.blob_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_BLOB_SIZE,\n+\t\t\t\t\t\t\t    stats->objects.disk_sizes.blobs),\n+\t\t\t      value_delim);\n \tprint_keyvalue(\"objects.tags.disk_size\", key_delim,\n \t\t       stats->objects.disk_sizes.tags, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.tags.disk_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_TAG_SIZE,\n+\t\t\t\t\t\t\t    stats->objects.disk_sizes.tags),\n+\t\t\t      value_delim);\n \n \tprint_object_data(\"objects.commits.max_size\", key_delim,\n \t\t\t  &stats->objects.largest.commit_size, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.commits.max_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_COMMIT_LARGEST,\n+\t\t\t\t\t\t\t    stats->objects.largest.commit_size.value),\n+\t\t\t      value_delim);\n \tprint_object_data(\"objects.trees.max_size\", key_delim,\n \t\t\t  &stats->objects.largest.tree_size, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.trees.max_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_TREE_LARGEST,\n+\t\t\t\t\t\t\t    stats->objects.largest.tree_size.value),\n+\t\t\t      value_delim);\n \tprint_object_data(\"objects.blobs.max_size\", key_delim,\n \t\t\t  &stats->objects.largest.blob_size, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.blobs.max_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_BLOB_LARGEST,\n+\t\t\t\t\t\t\t    stats->objects.largest.blob_size.value),\n+\t\t\t      value_delim);\n \tprint_object_data(\"objects.tags.max_size\", key_delim,\n \t\t\t  &stats->objects.largest.tag_size, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.tags.max_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_TAG_LARGEST,\n+\t\t\t\t\t\t\t    stats->objects.largest.tag_size.value),\n+\t\t\t      value_delim);\n \n \tprint_object_data(\"objects.commits.max_parents\", key_delim,\n \t\t\t  &stats->objects.largest.parent_count, value_delim);\n@@ -935,6 +1225,8 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,\n \tstruct repo_structure stats = { 0 };\n \tstruct rev_info revs;\n \tint show_progress = -1;\n+\tint show_concern = 0;\n+\n \tstruct option options[] = {\n \t\tOPT_CALLBACK_F(0, \"format\", &format, N_(\"format\"),\n \t\t\t       N_(\"output format\"),\n@@ -944,6 +1236,7 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,\n \t\t\t       PARSE_OPT_NONEG | PARSE_OPT_NOARG,\n \t\t\t       parse_format_cb),\n \t\tOPT_BOOL(0, \"progress\", &show_progress, N_(\"show progress\")),\n+\t\tOPT_BOOL(0, \"concern\", &show_concern, N_(\"show level of concern\")),\n \t\tOPT_END()\n \t};\n \n@@ -962,13 +1255,13 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,\n \tswitch (format) {\n \tcase FORMAT_TABLE:\n \t\tstats_table_setup_structure(&table, &stats);\n-\t\tstats_table_print_structure(&table);\n+\t\tstats_table_print_structure(&table, show_concern);\n \t\tbreak;\n \tcase FORMAT_NEWLINE_TERMINATED:\n-\t\tstructure_keyvalue_print(&stats, '=', '\\n');\n+\t\tstructure_keyvalue_print(&stats, '=', '\\n', show_concern);\n \t\tbreak;\n \tcase FORMAT_NUL_TERMINATED:\n-\t\tstructure_keyvalue_print(&stats, '\\n', '\\0');\n+\t\tstructure_keyvalue_print(&stats, '\\n', '\\0', show_concern);\n \t\tbreak;\n \tdefault:\n \t\tBUG(\"invalid output format\");\ndiff --git a/sideband.c b/sideband.c\nindex 374187402e..0e75ab2317 100644\n--- a/sideband.c\n+++ b/sideband.c\n@@ -1,4 +1,4 @@\n-#define USE_THE_REPOSITORY_VARIABLE\n+define USE_THE_REPOSITORY_VARIABLE\n #define DISABLE_SIGN_COMPARE_WARNINGS\n \n #include \"git-compat-util.h\"\ndiff --git a/t/t1901-repo-structure.sh b/t/t1901-repo-structure.sh\nindex 02cc2b594a..f1e3fd2a75 100755\n--- a/t/t1901-repo-structure.sh\n+++ b/t/t1901-repo-structure.sh\n@@ -67,77 +67,7 @@ test_expect_success 'empty repository' '\n \t\tEOF\n \n \t\tgit repo structure >out 2>err &&\n-\n-\t\ttest_cmp expect out &&\n-\t\ttest_line_count = 0 err\n-\t)\n-'\n-\n-test_expect_success SHA1 'repository with references and objects' '\n-\ttest_when_finished \"rm -rf repo\" &&\n-\tgit init repo &&\n-\t(\n-\t\tcd repo &&\n-\t\ttest_commit_bulk 1005 &&\n-\t\tgit tag -a foo -m bar &&\n-\n-\t\toid=\"$(git rev-parse HEAD)\" &&\n-\t\tgit update-ref refs/remotes/origin/foo \"$oid\" &&\n-\n-\t\t# Also creates a commit, tree, and blob.\n-\t\tgit notes add -m foo &&\n-\n-\t\t# The tags disk size is handled specially due to the\n-\t\t# git-rev-list(1) --disk-usage=human option printing the full\n-\t\t# \"byte/bytes\" unit string instead of just \"B\".\n-\t\tcat >expect <<-EOF &&\n-\t\t| Repository structure      | Value      |\n-\t\t| ------------------------- | ---------- |\n-\t\t| * References              |            |\n-\t\t|   * Count                 |      4     |\n-\t\t|     * Branches            |      1     |\n-\t\t|     * Tags                |      1     |\n-\t\t|     * Remotes             |      1     |\n-\t\t|     * Others              |      1     |\n-\t\t|                           |            |\n-\t\t| * Reachable objects       |            |\n-\t\t|   * Count                 |   3.02 k   |\n-\t\t|     * Commits             |   1.01 k   |\n-\t\t|     * Trees               |   1.01 k   |\n-\t\t|     * Blobs               |   1.01 k   |\n-\t\t|     * Tags                |      1     |\n-\t\t|   * Inflated size         |  16.03 MiB |\n-\t\t|     * Commits             | 217.92 KiB |\n-\t\t|     * Trees               |  15.81 MiB |\n-\t\t|     * Blobs               |  11.68 KiB |\n-\t\t|     * Tags                |    132 B   |\n-\t\t|   * Disk size             | $(object_type_disk_usage all true) |\n-\t\t|     * Commits             | $(object_type_disk_usage commit true) |\n-\t\t|     * Trees               | $(object_type_disk_usage tree true) |\n-\t\t|     * Blobs               |  $(object_type_disk_usage blob true) |\n-\t\t|     * Tags                |    $(object_type_disk_usage tag) B   |\n-\t\t|                           |            |\n-\t\t| * Largest objects         |            |\n-\t\t|   * Commits               |            |\n-\t\t|     * Maximum size    [1] |    223 B   |\n-\t\t|     * Maximum parents [2] |      1     |\n-\t\t|   * Trees                 |            |\n-\t\t|     * Maximum size    [3] |  32.29 KiB |\n-\t\t|     * Maximum entries [4] |   1.01 k   |\n-\t\t|   * Blobs                 |            |\n-\t\t|     * Maximum size    [5] |     13 B   |\n-\t\t|   * Tags                  |            |\n-\t\t|     * Maximum size    [6] |    132 B   |\n-\n-\t\t[1] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a\n-\t\t[2] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a\n-\t\t[3] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c\n-\t\t[4] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c\n-\t\t[5] 97d808e45116bf02103490294d3d46dad7a2ac62\n-\t\t[6] 4dae4f5954f5e6feb3577cfb1b181daa3fd3afd2\n-\t\tEOF\n-\n-\t\tgit repo structure >out 2>err &&\n+\t\tcp out /tmp/test1-out &&\n \n \t\ttest_cmp expect out &&\n \t\ttest_line_count = 0 err\n@@ -204,6 +134,59 @@ test_expect_success SHA1 'lines and nul format' '\n \t)\n '\n \n+test_expect_success SHA1 'lines format with concerns' '\n+\ttest_when_finished \"rm -rf repo\" &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\t# configure thresholds to get concern indications\n+\t\tgit config repo.structure.concern.commit-count 7 &&\n+\t\tgit config repo.structure.concern.commit-size 14 &&\n+\t\tgit config repo.structure.concern.tree-size 10 &&\n+\t\tgit config repo.structure.concern.blob-size 8 &&\n+\t\tgit config repo.structure.concern.blob-inflated-size 10 &&\n+\t\tgit config repo.structure.concern.tag-size 2 &&\n+\t\tgit config repo.structure.concern.commit-largest 4 &&\n+\t\tgit config repo.structure.concern.blob-largest 2 &&\n+\t\ttouch configured &&\n+\t\ttest_commit_bulk 1005 &&\n+\t\tgit tag -a foo -m bar &&\n+\t\toid=\"$(git rev-parse HEAD)\" &&\n+\t\tgit update-ref refs/remotes/origin/foo \"$oid\" &&\n+\n+\t\t# Also creates a commit, tree, and blob.\n+\t\tgit notes add -m foo &&\n+\n+\n+\t\t# The tags disk size is handled specially due to the\n+\t\t# git-rev-list(1) --disk-usage=human option printing the full\n+\t\t# \"byte/bytes\" unit string instead of just \"B\".\n+\t\tcat >expect <<-EOF &&\n+\t\tobjects.commits.count.concern_level=2\n+\t\tobjects.trees.count.concern_level=0\n+\t\tobjects.blobs.count.concern_level=0\n+\t\tobjects.tags.count.concern_level=0\n+\t\tobjects.commits.inflated_size.concern_level=0\n+\t\tobjects.trees.inflated_size.concern_level=0\n+\t\tobjects.blobs.inflated_size.concern_level=3\n+\t\tobjects.tags.inflated_size.concern_level=0\n+\t\tobjects.commits.disk_size.concern_level=3\n+\t\tobjects.trees.disk_size.concern_level=8\n+\t\tobjects.blobs.blob_size.concern_level=6\n+\t\tobjects.tags.disk_size.concern_level=4\n+\t\tobjects.commits.max_size.concern_level=3\n+\t\tobjects.trees.max_size.concern_level=0\n+\t\tobjects.blobs.max_size.concern_level=1\n+\t\tobjects.tags.max_size.concern_level=0\n+\t\tEOF\n+\n+\t\tgit repo structure --concern --format=lines >actual 2>err &&\n+\t\ttest_grep \"concern_level\" actual >actual-concerns &&\n+\t\ttest_cmp expect actual-concerns &&\n+\t\ttest_line_count = 0 err\n+\t)\n+'\n+\n test_expect_success 'progress meter option' '\n \ttest_when_finished \"rm -rf repo\" &&\n \tgit init repo &&\n\n-- \n2.53.0\n\n\n"},{"id":"555736","messageId":"20261021-b4-repo-structure-concern-v1-2-e3010552292c@fastmail.com","threadId":"66621","inReplyTo":"20261021-b4-repo-structure-concern-v1-0-e3010552292c@fastmail.com","subject":"[PATCH 2/3] repo: make concern thresholds configurable","fromName":"Mark C. Chu-Carroll","fromEmail":"markchucarroll@fastmail.com","sentAt":"2026-10-21T13:55:06Z","receivedAt":"2026-10-21T13:55:06Z","isPatch":true,"sender":{"key":"markchucarroll@fastmail.com","avatar":null},"body":"The concern reporting mechanism so far provides a\nmeasure of concern based on heuristic values estimated\nby analyzing a collection of (hopefully) representative\nrepositories. But for different projects/usecases, the\nthresholds at which a value becomes concerning may vary.\n\nProvide a mechanism using git config to allow users to\nconfigure per-repo concern levels for \"git repo structure\"\nconcern reporting.\n---\n builtin/repo.c            |  44 +++++++------\n sideband.c                |   2 +-\n t/t1901-repo-structure.sh | 155 +++++++++++++++++++++++++++++++++++++++++++++-\n 3 files changed, 178 insertions(+), 23 deletions(-)\n\ndiff --git a/builtin/repo.c b/builtin/repo.c\nindex a450497d4e..aa6efdfd25 100644\n--- a/builtin/repo.c\n+++ b/builtin/repo.c\n@@ -2,6 +2,7 @@\n \n #include \"builtin.h\"\n #include \"commit.h\"\n+#include \"config.h\"\n #include \"environment.h\"\n #include \"hash.h\"\n #include \"hex.h\"\n@@ -386,25 +387,27 @@ enum concern_stat {\n \tCONCERN_STAT_TREE_INFLATED_SIZE,\n \tCONCERN_STAT_BLOB_INFLATED_SIZE,\n \tCONCERN_STAT_TAG_INFLATED_SIZE,\n+\tCONCERN_STAT_MAXIMUM\n };\n \n-struct concern_stat_threshold repo_structure_concern_thresholds[] = {\n-\t{ .name = \"commit_count\", .threshold = 19 },\n-\t{.name = \"tree_count\", .threshold = 21 },\n-\t{ .name = \"blob_count\", .threshold = 22 },\n-\t{ .name = \"tag_count\", .threshold = 15 },\n-\t{ .name = \"commit_size\", .threshold = 28 },\n-\t{ .name = \"tree_size\", .threshold = 27 },\n-\t{ .name = \"blob_size\", .threshold = 36 },\n-\t{ .name = \"tags_size\", .threshold = 18 },\n-\t{ .name = \"commit_largest\", .threshold = 18  },\n-\t{ .name = \"tree_largest\", .threshold = 18 },\n-\t{ .name = \"blob_largest\", .threshold = 26 },\n-\t{ .name = \"tag_largest\", .threshold =  15 },\n-\t{ .name = \"commit_inflated_size\", .threshold = 28 },\n-\t{ .name = \"tree_inflated_size\", .threshold = 32 },\n-\t{ .name = \"blob_inflated_size\", .threshold = 34 },\n-\t{ .name = \"tag_inflated_size\", .threshold = 25}\n+struct concern_stat_threshold concern_stat_thresholds[] = {\n+\t{ .name = \"commit-count\", .threshold = 19 },\n+\t{.name = \"tree-count\", .threshold = 21 },\n+\t{ .name = \"blob-count\", .threshold = 22 },\n+\t{ .name = \"tag-count\", .threshold = 15 },\n+\t{ .name = \"commit-size\", .threshold = 28 },\n+\t{ .name = \"tree-size\", .threshold = 27 },\n+\t{ .name = \"blob-size\", .threshold = 36 },\n+\t{ .name = \"tag-size\", .threshold = 18 },\n+\t{ .name = \"commit-largest\", .threshold = 18  },\n+\t{ .name = \"tree-largest\", .threshold = 18 },\n+\t{ .name = \"blob-largest\", .threshold = 26 },\n+\t{ .name = \"tag-largest\", .threshold =  15 },\n+\t{ .name = \"commit-inflated-size\", .threshold = 28 },\n+\t{ .name = \"tree-inflated-size\", .threshold = 32 },\n+\t{ .name = \"blob-inflated-size\", .threshold = 34 },\n+\t{ .name = \"tag-inflated-size\", .threshold = 25},\n+\t{ 0 }\n };\n \n /*\n@@ -450,7 +453,7 @@ static size_t approx_log2(long value) {\n static size_t repo_structure_concern_level(enum concern_stat stat_id,\n \t\t\t\t\t   size_t value) {\n \tsize_t log_value;\n-\tif (stat_id >= ARRAY_SIZE(concern_stat_thresholds))\n+\tif (stat_id >= CONCERN_STAT_MAXIMUM)\n \t\tBUG(\"unexpected repository stat type\");\n \tlog_value = approx_log2(value);\n \tif (log_value > concern_stat_thresholds[stat_id].threshold)\n@@ -977,7 +980,7 @@ static void structure_keyvalue_print(struct repo_structure *stats,\n \tprint_keyvalue(\"objects.blobs.disk_size\", key_delim,\n \t\t       stats->objects.disk_sizes.blobs, value_delim);\n \tif (show_concern)\n-\t\tprint_keyvalue(\"objects.blobs.blob_size.concern_level\", key_delim,\n+\t\tprint_keyvalue(\"objects.blobs.disk_size.concern_level\", key_delim,\n \t\t\t       repo_structure_concern_level(CONCERN_STAT_BLOB_SIZE,\n \t\t\t\t\t\t\t    stats->objects.disk_sizes.blobs),\n \t\t\t      value_delim);\n@@ -1243,7 +1246,8 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,\n \targc = parse_options(argc, argv, prefix, options, repo_structure_usage, 0);\n \tif (argc)\n \t\tusage(_(\"too many arguments\"));\n-\n+\tif (show_concern)\n+\t\trepo_config(repo, &config_concern_thresholds, NULL);\n \trepo_init_revisions(repo, &revs, prefix);\n \n \tif (show_progress < 0)\ndiff --git a/sideband.c b/sideband.c\nindex 0e75ab2317..374187402e 100644\n--- a/sideband.c\n+++ b/sideband.c\n@@ -1,4 +1,4 @@\n-define USE_THE_REPOSITORY_VARIABLE\n+#define USE_THE_REPOSITORY_VARIABLE\n #define DISABLE_SIGN_COMPARE_WARNINGS\n \n #include \"git-compat-util.h\"\ndiff --git a/t/t1901-repo-structure.sh b/t/t1901-repo-structure.sh\nindex f1e3fd2a75..49ecc081ad 100755\n--- a/t/t1901-repo-structure.sh\n+++ b/t/t1901-repo-structure.sh\n@@ -67,13 +67,164 @@ test_expect_success 'empty repository' '\n \t\tEOF\n \n \t\tgit repo structure >out 2>err &&\n-\t\tcp out /tmp/test1-out &&\n \n \t\ttest_cmp expect out &&\n \t\ttest_line_count = 0 err\n \t)\n '\n \n+test_expect_success SHA1 'repository with references and objects' '\n+\ttest_when_finished \"rm -rf repo\" &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\ttest_commit_bulk 1005 &&\n+\t\tgit tag -a foo -m bar &&\n+\n+\t\toid=\"$(git rev-parse HEAD)\" &&\n+\t\tgit update-ref refs/remotes/origin/foo \"$oid\" &&\n+\n+\t\t# Also creates a commit, tree, and blob.\n+\t\tgit notes add -m foo &&\n+\n+\t\t# The tags disk size is handled specially due to the\n+\t\t# git-rev-list(1) --disk-usage=human option printing the full\n+\t\t# \"byte/bytes\" unit string instead of just \"B\".\n+\t\tcat >expect <<-EOF &&\n+\t\t| Repository structure      | Value      |\n+\t\t| ------------------------- | ---------- |\n+\t\t| * References              |            |\n+\t\t|   * Count                 |      4     |\n+\t\t|     * Branches            |      1     |\n+\t\t|     * Tags                |      1     |\n+\t\t|     * Remotes             |      1     |\n+\t\t|     * Others              |      1     |\n+\t\t|                           |            |\n+\t\t| * Reachable objects       |            |\n+\t\t|   * Count                 |   3.02 k   |\n+\t\t|     * Commits             |   1.01 k   |\n+\t\t|     * Trees               |   1.01 k   |\n+\t\t|     * Blobs               |   1.01 k   |\n+\t\t|     * Tags                |      1     |\n+\t\t|   * Inflated size         |  16.03 MiB |\n+\t\t|     * Commits             | 217.92 KiB |\n+\t\t|     * Trees               |  15.81 MiB |\n+\t\t|     * Blobs               |  11.68 KiB |\n+\t\t|     * Tags                |    132 B   |\n+\t\t|   * Disk size             | $(object_type_disk_usage all true) |\n+\t\t|     * Commits             | $(object_type_disk_usage commit true) |\n+\t\t|     * Trees               | $(object_type_disk_usage tree true) |\n+\t\t|     * Blobs               |  $(object_type_disk_usage blob true) |\n+\t\t|     * Tags                |    $(object_type_disk_usage tag) B   |\n+\t\t|                           |            |\n+\t\t| * Largest objects         |            |\n+\t\t|   * Commits               |            |\n+\t\t|     * Maximum size    [1] |    223 B   |\n+\t\t|     * Maximum parents [2] |      1     |\n+\t\t|   * Trees                 |            |\n+\t\t|     * Maximum size    [3] |  32.29 KiB |\n+\t\t|     * Maximum entries [4] |   1.01 k   |\n+\t\t|   * Blobs                 |            |\n+\t\t|     * Maximum size    [5] |     13 B   |\n+\t\t|   * Tags                  |            |\n+\t\t|     * Maximum size    [6] |    132 B   |\n+\n+\t\t[1] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a\n+\t\t[2] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a\n+\t\t[3] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c\n+\t\t[4] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c\n+\t\t[5] 97d808e45116bf02103490294d3d46dad7a2ac62\n+\t\t[6] 4dae4f5954f5e6feb3577cfb1b181daa3fd3afd2\n+\t\tEOF\n+\n+\t\tgit repo structure >out 2>err &&\n+\n+\t\ttest_cmp expect out &&\n+\t\ttest_line_count = 0 err\n+\t)\n+'\n+\n+test_expect_success SHA1 'concern flagging' '\n+\ttest_when_finished \"rm -rf repo\" &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\t# configure thresholds to get concern indications\n+\t\tgit config repo.structure.concern.commit-count 7 &&\n+\t\tgit config repo.structure.concern.commit-size 14 &&\n+\t\tgit config repo.structure.concern.tree-size 10 &&\n+\t\tgit config repo.structure.concern.blob-size 8 &&\n+\t\tgit config repo.structure.concern.blob-inflated-size 10 &&\n+\t\tgit config repo.structure.concern.tag-size 2 &&\n+\t\tgit config repo.structure.concern.commit-largest 4 &&\n+\t\tgit config repo.structure.concern.blob-largest 2 &&\n+\t\ttouch configured &&\n+\t\ttest_commit_bulk 1005 &&\n+\t\tgit tag -a foo -m bar &&\n+\t\toid=\"$(git rev-parse HEAD)\" &&\n+\t\tgit update-ref refs/remotes/origin/foo \"$oid\" &&\n+\n+\t\t# Also creates a commit, tree, and blob.\n+\t\tgit notes add -m foo &&\n+\n+\n+\t\t# The tags disk size is handled specially due to the\n+\t\t# git-rev-list(1) --disk-usage=human option printing the full\n+\t\t# \"byte/bytes\" unit string instead of just \"B\".\n+\t\tcat >expect <<-EOF &&\n+\t\t| Repository structure      | Value      | Concern  |\n+\t\t| ------------------------- | ---------- | -------- |\n+\t\t| * References              |            |          |\n+\t\t|   * Count                 |      4     |          |\n+\t\t|     * Branches            |      1     |          |\n+\t\t|     * Tags                |      1     |          |\n+\t\t|     * Remotes             |      1     |          |\n+\t\t|     * Others              |      1     |          |\n+\t\t|                           |            |          |\n+\t\t| * Reachable objects       |            |          |\n+\t\t|   * Count                 |   3.02 k   |          |\n+\t\t|     * Commits             |   1.01 k   | **       |\n+\t\t|     * Trees               |   1.01 k   |          |\n+\t\t|     * Blobs               |   1.01 k   |          |\n+\t\t|     * Tags                |      1     |          |\n+\t\t|   * Inflated size         |  16.03 MiB |          |\n+\t\t|     * Commits             | 217.92 KiB |          |\n+\t\t|     * Trees               |  15.81 MiB |          |\n+\t\t|     * Blobs               |  11.68 KiB | ***      |\n+\t\t|     * Tags                |    132 B   |          |\n+\t\t|   * Disk size             | $(object_type_disk_usage all true) |          |\n+\t\t|     * Commits             | $(object_type_disk_usage commit true) | ***      |\n+\t\t|     * Trees               | $(object_type_disk_usage tree true) | ******** |\n+\t\t|     * Blobs               |  $(object_type_disk_usage blob true) | ******   |\n+\t\t|     * Tags                |    $(object_type_disk_usage tag) B   | ****     |\n+\t\t|                           |            |          |\n+\t\t| * Largest objects         |            |          |\n+\t\t|   * Commits               |            |          |\n+\t\t|     * Maximum size    [1] |    223 B   | ***      |\n+\t\t|     * Maximum parents [2] |      1     |          |\n+\t\t|   * Trees                 |            |          |\n+\t\t|     * Maximum size    [3] |  32.29 KiB |          |\n+\t\t|     * Maximum entries [4] |   1.01 k   |          |\n+\t\t|   * Blobs                 |            |          |\n+\t\t|     * Maximum size    [5] |     13 B   | *        |\n+\t\t|   * Tags                  |            |          |\n+\t\t|     * Maximum size    [6] |    132 B   |          |\n+\n+\t\t[1] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a\n+\t\t[2] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a\n+\t\t[3] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c\n+\t\t[4] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c\n+\t\t[5] 97d808e45116bf02103490294d3d46dad7a2ac62\n+\t\t[6] 4dae4f5954f5e6feb3577cfb1b181daa3fd3afd2\n+\t\tEOF\n+\n+\t\tgit repo structure --concern >actual 2>err &&\n+\n+\t\ttest_cmp expect actual &&\n+\t\ttest_line_count = 0 err\n+\t)\n+'\n+\n test_expect_success SHA1 'lines and nul format' '\n \ttest_when_finished \"rm -rf repo\" &&\n \tgit init repo &&\n@@ -172,7 +323,7 @@ test_expect_success SHA1 'lines format with concerns' '\n \t\tobjects.tags.inflated_size.concern_level=0\n \t\tobjects.commits.disk_size.concern_level=3\n \t\tobjects.trees.disk_size.concern_level=8\n-\t\tobjects.blobs.blob_size.concern_level=6\n+\t\tobjects.blobs.disk_size.concern_level=6\n \t\tobjects.tags.disk_size.concern_level=4\n \t\tobjects.commits.max_size.concern_level=3\n \t\tobjects.trees.max_size.concern_level=0\n\n-- \n2.53.0\n\n\n"},{"id":"555737","messageId":"20261021-b4-repo-structure-concern-v1-3-e3010552292c@fastmail.com","threadId":"66621","inReplyTo":"20261021-b4-repo-structure-concern-v1-0-e3010552292c@fastmail.com","subject":"[PATCH 3/3] repo: add documentation for level-of-concern.","fromName":"Mark C. Chu-Carroll","fromEmail":"markchucarroll@fastmail.com","sentAt":"2026-10-21T13:55:07Z","receivedAt":"2026-10-21T13:55:07Z","isPatch":true,"sender":{"key":"markchucarroll@fastmail.com","avatar":null},"body":"---\n Documentation/config/repo.adoc | 60 ++++++++++++++++++++++++++++++++++++++++++\n Documentation/git-repo.adoc    | 27 +++++++++++++++++--\n 2 files changed, 85 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/config/repo.adoc b/Documentation/config/repo.adoc\nnew file mode 100644\nindex 0000000000..ad16bdcc67\n--- /dev/null\n+++ b/Documentation/config/repo.adoc\n@@ -0,0 +1,60 @@\n+`repo.structure.concern.commit-count`::\n+\tSets the value of the concern threshold for commit count.\n+\n+`repo.structure.concern.tree-count`::\n+\tSets the value of the concern threshold for tree count.\n+\n+`repo.structure.concern.blob-count`::\n+\tSets the value of the concern threshold for blob count.\n+\n+`repo.structure.concern.tag-count`::\n+\tSets the value of the concern threshold for tag count.\n+\n+`repo.structure.concern.commit-size`::\n+\tSets the value of the concern threshold for total commit size.\n+\n+`repo.structure.concern.tree-size`::\n+\tSets the value of the concern threshold for total tree size.\n+\n+`repo.structure.concern.blob-size`::\n+\tSets the value of the concern threshold for total blob size.\n+\n+`repo.structure.concern.tag-size`::\n+\tSets the value of the concern threshold for total tag size.\n+\n+`repo.structure.concern.commit-inflated-size`::\n+\tSets the value of the concern threshold for total size\n+\tof all inflated commits.\n+\n+`repo.structure.concern.tree-inflated-size`::\n+\tSets the value of the concern threshold for total size of\n+\tall inflated trees.\n+\n+`repo.structure.concern.blob-inflated-size`::\n+\tSets the value of the concern threshold for total size of\n+\tall inflated blobs.\n+\n+`repo.structure.concern.tag-inflated-size`::\n+\tSets the value of the concern threshold for the total size\n+\tof all inflated tags.\n+\n+`repo.structure.concern.commit-largest`::\n+\tSets the value of the concern threshold the size of the largest\n+\tcommit.\n+\n+`repo.structure.concern.tree-largest`::\n+\tSets the value of the concern threshold for the size of the\n+\tlargest tree.\n+\n+`repo.structure.concern.blob-largest`::\n+\tSets the value of the concern threshold for the size of the\n+\tlargest blob.\n+\n+`repo.structure.concern.tag-largest`::\n+\tSets the value of the concern threshold for the size of the\n+\tlargest tag.\n++\n+All of the concern thresholds are integer values for the \n+floor of the base-2 log of the threshold value. The default values\n+of each threshold are set to heuristically determined values.\n+\ndiff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc\nindex ed7d80c690..3c1045eb40 100644\n--- a/Documentation/git-repo.adoc\n+++ b/Documentation/git-repo.adoc\n@@ -10,7 +10,7 @@ SYNOPSIS\n [synopsis]\n git repo info [--format=(lines|nul) | -z] [--all | <key>...]\n git repo info --keys [--format=(lines|nul) | -z]\n-git repo structure [--format=(table|lines|nul) | -z]\n+git repo structure [--format=(table|lines|nul) | -z] [--[no-]concern]\n \n DESCRIPTION\n -----------\n@@ -56,7 +56,7 @@ supported:\n `nul`:::\n \tSimilar to `lines`, but using a _NUL_ character after each value.\n \n-`structure [--format=(table|lines|nul) | -z]`::\n+`structure [--format=(table|lines|nul) | -z] [--[no-]concern]`::\n \tRetrieve statistics about the current repository structure. The\n \tfollowing kinds of information are reported:\n +\n@@ -88,6 +88,20 @@ supported:\n \tquoted.\n +\n `-z` is an alias for `--format=nul`.\n++\n+To make it easier to know when one of the values is something that\n+users should be concerned about, adding '--concern' will provide a\n+level of concern to be added to the output. In 'table' mode, this will\n+be a table column indicating the level of concern as a number of stars;\n+in 'lines` or `nul' mode, a `concern_level` line will be added for each\n+statistic with a numeric value.\n++\n+The level of corcern for each statistic is computed using the base_2 log of the\n+statistic value. The value is considered concerning if it exceeds a heuristically\n+chosen threshold value; the degree of concern is the difference between the\n+base_2 log of the value, and the base_2 log of the corresponding threshold.\n++\n+The threshold values are configurable; see the configuration section below.\n \n INFO KEYS\n ---------\n@@ -141,6 +155,15 @@ using the `nul` format:\n git repo info --format=nul layout.bare layout.shallow\n ------------\n \n+CONFIGURATION\n+-------------\n+\n+include::includes/cmd-config-section-all.adoc[]\n+\n+:git-repo: 1\n+include::config/repo.adoc[]\n+\n+\n SEE ALSO\n --------\n linkgit:git-rev-parse[1]\n\n-- \n2.53.0\n\n\n"},{"id":"556381","messageId":"20261026-b4-repo-structure-concern-v2-0-e0df34228dc8@fastmail.com","threadId":"66621","inReplyTo":"20261021-b4-repo-structure-concern-v1-0-e3010552292c@fastmail.com","subject":"[PATCH v2 0/3] repo: add degree-of-concern reporting to structure","fromName":"Mark C. Chu-Carroll","fromEmail":"markchucarroll@fastmail.com","sentAt":"2026-10-26T17:43:53Z","receivedAt":"2026-10-26T17:43:53Z","isPatch":true,"sender":{"key":"markchucarroll@fastmail.com","avatar":null},"body":"\"git repo structure\" computes a set of statistics that are useful for\ndiagnosing performance issues in a git repository. But for a typical\nuser who hasn't encountered any issues yet, it can be difficult to\ninterpret the data - when should a value be considered concerning?\n\nThis patchset introduces a feature to \"git repo structure\" that reports\non how concerning certain measures of storage structure  are for a\nrepository.  In table mode, concern is displayed as a column containing\na number of stars indicating the degree of concern; in line or nul mode,\nconcerns are displayed as additionaL lines with a numeric value.\n\nTo select defaults for the thresholds where a value becomes concern,\na (hopefully) representative selection of repositories were analyzed.\nBased on empirical exploration of the data, the measurement that seemed\nclosest to accurately assessing when a repository's structure becomes\nconcerning was based on the base 2 logarithm of the value. Across all of\nthe sample repositories, for all of the measurements, the point at which\nthings become problematic was when a base 2 logarithm of a value was\nmore than 1 standard deviation from the mean of the log_2 values in\nthe sample set.  Based on this, the concern threshold was set for each\nof the measures.\n\nWhile this seems like a reasonable approach for setting default\nthresholds, it's obviously still very ad-hoc. It's likely that over\ntime, the default values will evolve; and in the short term, projects\nmay want to set their own thresholds. So this patchset includes support\nfor configuring the thresholds on a per-reposiry basis.\n\nOverview of the change:\n\n* The first commit adds the command-line option \"--concern\", and creates the basic implementation of concern reporting.\n* The next commit adds configurability for the concern thresholds.\n* The final commit update the documentation for the command and\n  configuration options.\n\nSigned-off-by: Mark C. Chu-Carroll <markchucarroll@fastmail.com>\n---\nChanges in v2:\n- Fix memory leak in concern string generation.\n- Fix CI/CD test issue involving zlib versions.\n- Updating the naming of the config value prefix.\n- Link to v1: https://patch.msgid.link/20261021-b4-repo-structure-concern-v1-0-e3010552292c@fastmail.com\n\n---\nMark C. Chu-Carroll (3):\n      repo: add level of concern reporting\n      repo: make concern thresholds configurable\n      repo: add documentation for level-of-concern.\n\n Documentation/config/repo.adoc |  59 +++++++\n Documentation/git-repo.adoc    |  27 +++-\n builtin/repo.c                 | 343 ++++++++++++++++++++++++++++++++++++++---\n t/t1901-repo-structure.sh      | 137 ++++++++++++++++\n 4 files changed, 543 insertions(+), 23 deletions(-)\n\nRange-diff versus v1:\n\n1:  c2b2a79967 = 1:  e8971a4050 repo: add level of concern reporting\n2:  67d77cfcd9 ! 2:  7a78eee517 repo: make concern thresholds configurable\n    @@ builtin/repo.c\n      #include \"environment.h\"\n      #include \"hash.h\"\n      #include \"hex.h\"\n    +@@\n    + #define REPO_STRUCTURE_USAGE \\\n    + \t\"git repo structure [--format=(table|lines|nul) | -z] [--[no-]concern]\"\n    + \n    ++#define REPO_STRUCTURE_CONFIG_PREFIX \"repo.concern.\"\n    ++\n    + static const char *const repo_usage[] = {\n    + \tREPO_INFO_USAGE,\n    + \tREPO_STRUCTURE_USAGE,\n     @@ builtin/repo.c: enum concern_stat {\n      \tCONCERN_STAT_TREE_INFLATED_SIZE,\n      \tCONCERN_STAT_BLOB_INFLATED_SIZE,\n    @@ builtin/repo.c: enum concern_stat {\n     -\t{ .name = \"tag_inflated_size\", .threshold = 25}\n     +struct concern_stat_threshold concern_stat_thresholds[] = {\n     +\t{ .name = \"commit-count\", .threshold = 19 },\n    -+\t{.name = \"tree-count\", .threshold = 21 },\n    ++\t{ .name = \"tree-count\", .threshold = 21 },\n     +\t{ .name = \"blob-count\", .threshold = 22 },\n     +\t{ .name = \"tag-count\", .threshold = 15 },\n     +\t{ .name = \"commit-size\", .threshold = 28 },\n    @@ builtin/repo.c: enum concern_stat {\n      };\n      \n      /*\n    +  * Concern thresholds aren't universal. We try to set reasonable default\n    +  * values, but projects may want to set these thresholds differently. To\n    +  * support that, they can set the thresholds for their project in the\n    +- * project's git config, in the section \"repo.structure.concern\".\n    ++ * project's git config, in the section \"repo.concern\".\n    +   */\n    + static int config_concern_thresholds(const char *prefixed_var,\n    + \t\t\t\t     const char *str_val,\n    + \t\t\t\t     const struct config_context *ctx,\n    + \t\t\t\t     void *cb UNUSED) {\n    +-\tconst char *config_prefix = \"repo.structure.concern.\";\n    +-\tif (!strncmp(config_prefix, prefixed_var, strlen(config_prefix))) {\n    +-\t\tconst char *concern_name = &prefixed_var[strlen(config_prefix)];\n    ++\tif (!strncmp(REPO_STRUCTURE_CONFIG_PREFIX, prefixed_var,\n    ++\t      strlen(REPO_STRUCTURE_CONFIG_PREFIX))) {\n    ++\t\tconst char *concern_name =\n    ++\t\t\t&prefixed_var[strlen(REPO_STRUCTURE_CONFIG_PREFIX)];\n    + \t\tfor (int i = 0; concern_stat_thresholds[i].name != 0; i++) {\n    + \t\t\tif (!strcmp(concern_stat_thresholds[i].name, concern_name)) {\n    + \t\t\t\tint value = git_config_int(prefixed_var, str_val, ctx->kvi);\n    +@@ builtin/repo.c: static int config_concern_thresholds(const char *prefixed_var,\n    + \treturn 0;\n    + }\n    + \n    +-static size_t approx_log2(long value) {\n    ++static size_t approx_log2(size_t value) {\n    + \tint position = 0;\n    + \tif (value <= 0) {\n    + \t\treturn 0;\n     @@ builtin/repo.c: static size_t approx_log2(long value) {\n      static size_t repo_structure_concern_level(enum concern_stat stat_id,\n      \t\t\t\t\t   size_t value) {\n    @@ builtin/repo.c: static size_t approx_log2(long value) {\n      \t\tBUG(\"unexpected repository stat type\");\n      \tlog_value = approx_log2(value);\n      \tif (log_value > concern_stat_thresholds[stat_id].threshold)\n    +@@ builtin/repo.c: static void stats_table_print_structure(const struct stats_table *table,\n    + \t\t\tstrbuf_utf8_align(&buf, ALIGN_LEFT, concern_col_width, concern_str.buf);\n    + \t\t\tstrbuf_addstr(&buf, \" |\");\n    + \t\t}\n    ++\t\tstrbuf_release(&concern_str);\n    + \t\tprintf(\"%s\\n\", buf.buf);\n    + \t}\n    + \n     @@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stats,\n      \tprint_keyvalue(\"objects.blobs.disk_size\", key_delim,\n      \t\t       stats->objects.disk_sizes.blobs, value_delim);\n    @@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stat\n      \t\t\t       repo_structure_concern_level(CONCERN_STAT_BLOB_SIZE,\n      \t\t\t\t\t\t\t    stats->objects.disk_sizes.blobs),\n      \t\t\t      value_delim);\n    +@@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stats,\n    + \t\t\t\t\t\t\t    stats->objects.largest.commit_size.value),\n    + \t\t\t      value_delim);\n    + \tprint_object_data(\"objects.trees.max_size\", key_delim,\n    +-\t\t\t  &stats->objects.largest.tree_size, value_delim);\n    ++\t\t\t &stats->objects.largest.tree_size, value_delim);\n    + \tif (show_concern)\n    + \t\tprint_keyvalue(\"objects.trees.max_size.concern_level\", key_delim,\n    + \t\t\t       repo_structure_concern_level(CONCERN_STAT_TREE_LARGEST,\n    +@@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stats,\n    + \t\t\t\t\t\t\t    stats->objects.largest.blob_size.value),\n    + \t\t\t      value_delim);\n    + \tprint_object_data(\"objects.tags.max_size\", key_delim,\n    +-\t\t\t  &stats->objects.largest.tag_size, value_delim);\n    ++\t\t\t   &stats->objects.largest.tag_size, value_delim);\n    + \tif (show_concern)\n    + \t\tprint_keyvalue(\"objects.tags.max_size.concern_level\", key_delim,\n    + \t\t\t       repo_structure_concern_level(CONCERN_STAT_TAG_LARGEST,\n     @@ builtin/repo.c: static int cmd_repo_structure(int argc, const char **argv, const char *prefix,\n      \targc = parse_options(argc, argv, prefix, options, repo_structure_usage, 0);\n      \tif (argc)\n    @@ t/t1901-repo-structure.sh: test_expect_success 'empty repository' '\n     +\tgit init repo &&\n     +\t(\n     +\t\tcd repo &&\n    ++\t\t# differences between zlib and zlib-ng can cause this\n    ++\t\t# test to fail; so we just turn it off.\n    ++\t\tgit config core.compression 0 &&\n     +\t\t# configure thresholds to get concern indications\n    -+\t\tgit config repo.structure.concern.commit-count 7 &&\n    -+\t\tgit config repo.structure.concern.commit-size 14 &&\n    -+\t\tgit config repo.structure.concern.tree-size 10 &&\n    -+\t\tgit config repo.structure.concern.blob-size 8 &&\n    -+\t\tgit config repo.structure.concern.blob-inflated-size 10 &&\n    -+\t\tgit config repo.structure.concern.tag-size 2 &&\n    -+\t\tgit config repo.structure.concern.commit-largest 4 &&\n    -+\t\tgit config repo.structure.concern.blob-largest 2 &&\n    -+\t\ttouch configured &&\n    ++\t\tgit config repo.concern.commit-count 7 &&\n    ++\t\tgit config repo.concern.commit-size 14 &&\n    ++\t\tgit config repo.concern.tree-size 10 &&\n    ++\t\tgit config repo.concern.blob-size 8 &&\n    ++\t\tgit config repo.concern.blob-inflated-size 10 &&\n    ++\t\tgit config repo.concern.tag-size 2 &&\n    ++\t\tgit config repo.concern.commit-largest 4 &&\n    ++\t\tgit config repo.concern.blob-largest 2 &&\n     +\t\ttest_commit_bulk 1005 &&\n     +\t\tgit tag -a foo -m bar &&\n     +\t\toid=\"$(git rev-parse HEAD)\" &&\n    @@ t/t1901-repo-structure.sh: test_expect_success 'empty repository' '\n     +\t\t|     * Commits             | $(object_type_disk_usage commit true) | ***      |\n     +\t\t|     * Trees               | $(object_type_disk_usage tree true) | ******** |\n     +\t\t|     * Blobs               |  $(object_type_disk_usage blob true) | ******   |\n    -+\t\t|     * Tags                |    $(object_type_disk_usage tag) B   | ****     |\n    ++\t\t|     * Tags                |    $(object_type_disk_usage tag) B   | *****    |\n     +\t\t|                           |            |          |\n     +\t\t| * Largest objects         |            |          |\n     +\t\t|   * Commits               |            |          |\n    @@ t/t1901-repo-structure.sh: test_expect_success 'empty repository' '\n      test_expect_success SHA1 'lines and nul format' '\n      \ttest_when_finished \"rm -rf repo\" &&\n      \tgit init repo &&\n    +@@ t/t1901-repo-structure.sh: test_expect_success SHA1 'lines format with concerns' '\n    + \tgit init repo &&\n    + \t(\n    + \t\tcd repo &&\n    ++\t\t# differences between zlib and zlib-ng can cause this\n    ++\t\t# test to fail; so we just turn it off.\n    ++\t\tgit config core.compression 0 &&\n    + \t\t# configure thresholds to get concern indications\n    +-\t\tgit config repo.structure.concern.commit-count 7 &&\n    +-\t\tgit config repo.structure.concern.commit-size 14 &&\n    +-\t\tgit config repo.structure.concern.tree-size 10 &&\n    +-\t\tgit config repo.structure.concern.blob-size 8 &&\n    +-\t\tgit config repo.structure.concern.blob-inflated-size 10 &&\n    +-\t\tgit config repo.structure.concern.tag-size 2 &&\n    +-\t\tgit config repo.structure.concern.commit-largest 4 &&\n    +-\t\tgit config repo.structure.concern.blob-largest 2 &&\n    +-\t\ttouch configured &&\n    ++\t\tgit config repo.concern.commit-count 7 &&\n    ++\t\tgit config repo.concern.commit-size 14 &&\n    ++\t\tgit config repo.concern.tree-size 10 &&\n    ++\t\tgit config repo.concern.blob-size 8 &&\n    ++\t\tgit config repo.concern.blob-inflated-size 10 &&\n    ++\t\tgit config repo.concern.tag-size 2 &&\n    ++\t\tgit config repo.concern.commit-largest 4 &&\n    ++\t\tgit config repo.concern.blob-largest 2 &&\n    + \t\ttest_commit_bulk 1005 &&\n    + \t\tgit tag -a foo -m bar &&\n    + \t\toid=\"$(git rev-parse HEAD)\" &&\n    +@@ t/t1901-repo-structure.sh: test_expect_success SHA1 'lines format with concerns' '\n    + \t\t# Also creates a commit, tree, and blob.\n    + \t\tgit notes add -m foo &&\n    + \n    +-\n    + \t\t# The tags disk size is handled specially due to the\n    + \t\t# git-rev-list(1) --disk-usage=human option printing the full\n    + \t\t# \"byte/bytes\" unit string instead of just \"B\".\n     @@ t/t1901-repo-structure.sh: test_expect_success SHA1 'lines format with concerns' '\n      \t\tobjects.tags.inflated_size.concern_level=0\n      \t\tobjects.commits.disk_size.concern_level=3\n      \t\tobjects.trees.disk_size.concern_level=8\n     -\t\tobjects.blobs.blob_size.concern_level=6\n    +-\t\tobjects.tags.disk_size.concern_level=4\n     +\t\tobjects.blobs.disk_size.concern_level=6\n    - \t\tobjects.tags.disk_size.concern_level=4\n    ++\t\tobjects.tags.disk_size.concern_level=5\n      \t\tobjects.commits.max_size.concern_level=3\n      \t\tobjects.trees.max_size.concern_level=0\n    + \t\tobjects.blobs.max_size.concern_level=1\n3:  ac0adb7c9f ! 3:  cc78eaeca0 repo: add documentation for level-of-concern.\n    @@ Commit message\n     \n      ## Documentation/config/repo.adoc (new) ##\n     @@\n    -+`repo.structure.concern.commit-count`::\n    ++`repo.concern.commit-count`::\n     +\tSets the value of the concern threshold for commit count.\n     +\n    -+`repo.structure.concern.tree-count`::\n    ++`repo.concern.tree-count`::\n     +\tSets the value of the concern threshold for tree count.\n     +\n    -+`repo.structure.concern.blob-count`::\n    ++`repo.concern.blob-count`::\n     +\tSets the value of the concern threshold for blob count.\n     +\n    -+`repo.structure.concern.tag-count`::\n    ++`repo.concern.tag-count`::\n     +\tSets the value of the concern threshold for tag count.\n     +\n    -+`repo.structure.concern.commit-size`::\n    ++`repo.concern.commit-size`::\n     +\tSets the value of the concern threshold for total commit size.\n     +\n    -+`repo.structure.concern.tree-size`::\n    ++`repo.concern.tree-size`::\n     +\tSets the value of the concern threshold for total tree size.\n     +\n    -+`repo.structure.concern.blob-size`::\n    ++`repo.concern.blob-size`::\n     +\tSets the value of the concern threshold for total blob size.\n     +\n    -+`repo.structure.concern.tag-size`::\n    ++`repo.concern.tag-size`::\n     +\tSets the value of the concern threshold for total tag size.\n     +\n    -+`repo.structure.concern.commit-inflated-size`::\n    ++`repo.concern.commit-inflated-size`::\n     +\tSets the value of the concern threshold for total size\n     +\tof all inflated commits.\n     +\n    -+`repo.structure.concern.tree-inflated-size`::\n    ++`repo.concern.tree-inflated-size`::\n     +\tSets the value of the concern threshold for total size of\n     +\tall inflated trees.\n     +\n    -+`repo.structure.concern.blob-inflated-size`::\n    ++`repo.concern.blob-inflated-size`::\n     +\tSets the value of the concern threshold for total size of\n     +\tall inflated blobs.\n     +\n    -+`repo.structure.concern.tag-inflated-size`::\n    ++`repo.concern.tag-inflated-size`::\n     +\tSets the value of the concern threshold for the total size\n     +\tof all inflated tags.\n     +\n    -+`repo.structure.concern.commit-largest`::\n    ++`repo.concern.commit-largest`::\n     +\tSets the value of the concern threshold the size of the largest\n     +\tcommit.\n     +\n    -+`repo.structure.concern.tree-largest`::\n    ++`repo.concern.tree-largest`::\n     +\tSets the value of the concern threshold for the size of the\n     +\tlargest tree.\n     +\n    -+`repo.structure.concern.blob-largest`::\n    ++`repo.concern.blob-largest`::\n     +\tSets the value of the concern threshold for the size of the\n     +\tlargest blob.\n     +\n    -+`repo.structure.concern.tag-largest`::\n    ++`repo.concern.tag-largest`::\n     +\tSets the value of the concern threshold for the size of the\n     +\tlargest tag.\n    -++\n    -+All of the concern thresholds are integer values for the \n    ++\n    ++All of the concern thresholds are integer values for the\n     +floor of the base-2 log of the threshold value. The default values\n     +of each threshold are set to heuristically determined values.\n    -+\n     \n      ## Documentation/git-repo.adoc ##\n     @@ Documentation/git-repo.adoc: SYNOPSIS\n    @@ Documentation/git-repo.adoc: supported:\n     +users should be concerned about, adding '--concern' will provide a\n     +level of concern to be added to the output. In 'table' mode, this will\n     +be a table column indicating the level of concern as a number of stars;\n    -+in 'lines` or `nul' mode, a `concern_level` line will be added for each\n    ++in `lines` or `nul` mode, a `concern_level` line will be added for each\n     +statistic with a numeric value.\n     ++\n    -+The level of corcern for each statistic is computed using the base_2 log of the\n    ++The level of concern for each statistic is computed using the log_2 of the\n     +statistic value. The value is considered concerning if it exceeds a heuristically\n     +chosen threshold value; the degree of concern is the difference between the\n    -+base_2 log of the value, and the base_2 log of the corresponding threshold.\n    ++log_2 of the value, and the log_2 of the corresponding threshold.\n     ++\n     +The threshold values are configurable; see the configuration section below.\n      \n\n---\nbase-commit: 3bc0341126508f78f5869cbfc0005e987efdf0c7\nchange-id: 20261019-b4-repo-structure-concern-f1d6f6326095\n\n\n"},{"id":"556382","messageId":"20261026-b4-repo-structure-concern-v2-1-e0df34228dc8@fastmail.com","threadId":"66621","inReplyTo":"20261026-b4-repo-structure-concern-v2-0-e0df34228dc8@fastmail.com","subject":"[PATCH v2 1/3] repo: add level of concern reporting","fromName":"Mark C. Chu-Carroll","fromEmail":"markchucarroll@fastmail.com","sentAt":"2026-10-26T17:43:54Z","receivedAt":"2026-10-26T17:43:54Z","isPatch":true,"sender":{"key":"markchucarroll@fastmail.com","avatar":null},"body":"\"git repo structure\" computes a set of statistics\nthat are useful for diagnosing performance issues in\na git repository. But for a typical user who hasn't\nencountered any issues yet, it can be difficult to\ninterpret the data - when should a value be considered\nconcerning?\n\nAdd an option to \"git repo structure\" that adds a \"degree\nof concern\" column to the structure report, which indicates\nwhen a stat values has reached a point where it might be\nconcerning, and indicate how severe the concern should be.\n---\n builtin/repo.c            | 329 +++++++++++++++++++++++++++++++++++++++++++---\n sideband.c                |   2 +-\n t/t1901-repo-structure.sh | 125 ++++++++----------\n 3 files changed, 366 insertions(+), 90 deletions(-)\n\ndiff --git a/builtin/repo.c b/builtin/repo.c\nindex 84e012f83f..a450497d4e 100644\n--- a/builtin/repo.c\n+++ b/builtin/repo.c\n@@ -27,7 +27,7 @@\n \t\"git repo info --keys [--format=(lines|nul) | -z]\"\n \n #define REPO_STRUCTURE_USAGE \\\n-\t\"git repo structure [--format=(table|lines|nul) | -z]\"\n+\t\"git repo structure [--format=(table|lines|nul) | -z] [--[no-]concern]\"\n \n static const char *const repo_usage[] = {\n \tREPO_INFO_USAGE,\n@@ -348,8 +348,118 @@ struct stats_table {\n \tint name_col_width;\n \tint value_col_width;\n \tint unit_col_width;\n+\tint concern_col_width;\n };\n \n+/*\n+ * For computing level of concern about a stat, we need to have some\n+ * baseline values. For each of these, we'define a minimum level\n+ * of concern, which will appear as 1 star. Then we scale exponentially:\n+ * each time that value doubles, we add another star.\n+ *\n+ * The threshold values for concern are expressed as log_2 values of the size\n+ * at which the statistic becomes concerning. The default values for the\n+ * threshold were selected based on observed values from a collection of\n+ * representative-ish repositories of varying size, by finding the mean log_2\n+ * value and standard deviation (sigma) for each statistic over the collection.\n+ * The threshold for each metric is then set at mean(log_2) + sigma.\n+ */\n+struct concern_stat_threshold {\n+\tconst char *name;\n+\tsize_t threshold;\n+};\n+\n+enum concern_stat {\n+\tCONCERN_STAT_COMMIT_COUNT,\n+\tCONCERN_STAT_TREE_COUNT,\n+\tCONCERN_STAT_BLOB_COUNT,\n+\tCONCERN_STAT_TAG_COUNT,\n+\tCONCERN_STAT_COMMIT_SIZE,\n+\tCONCERN_STAT_TREE_SIZE,\n+\tCONCERN_STAT_BLOB_SIZE,\n+\tCONCERN_STAT_TAG_SIZE,\n+\tCONCERN_STAT_COMMIT_LARGEST,\n+\tCONCERN_STAT_TREE_LARGEST,\n+\tCONCERN_STAT_BLOB_LARGEST,\n+\tCONCERN_STAT_TAG_LARGEST,\n+\tCONCERN_STAT_COMMIT_INFLATED_SIZE,\n+\tCONCERN_STAT_TREE_INFLATED_SIZE,\n+\tCONCERN_STAT_BLOB_INFLATED_SIZE,\n+\tCONCERN_STAT_TAG_INFLATED_SIZE,\n+};\n+\n+struct concern_stat_threshold repo_structure_concern_thresholds[] = {\n+\t{ .name = \"commit_count\", .threshold = 19 },\n+\t{.name = \"tree_count\", .threshold = 21 },\n+\t{ .name = \"blob_count\", .threshold = 22 },\n+\t{ .name = \"tag_count\", .threshold = 15 },\n+\t{ .name = \"commit_size\", .threshold = 28 },\n+\t{ .name = \"tree_size\", .threshold = 27 },\n+\t{ .name = \"blob_size\", .threshold = 36 },\n+\t{ .name = \"tags_size\", .threshold = 18 },\n+\t{ .name = \"commit_largest\", .threshold = 18  },\n+\t{ .name = \"tree_largest\", .threshold = 18 },\n+\t{ .name = \"blob_largest\", .threshold = 26 },\n+\t{ .name = \"tag_largest\", .threshold =  15 },\n+\t{ .name = \"commit_inflated_size\", .threshold = 28 },\n+\t{ .name = \"tree_inflated_size\", .threshold = 32 },\n+\t{ .name = \"blob_inflated_size\", .threshold = 34 },\n+\t{ .name = \"tag_inflated_size\", .threshold = 25}\n+};\n+\n+/*\n+ * Concern thresholds aren't universal. We try to set reasonable default\n+ * values, but projects may want to set these thresholds differently. To\n+ * support that, they can set the thresholds for their project in the\n+ * project's git config, in the section \"repo.structure.concern\".\n+  */\n+static int config_concern_thresholds(const char *prefixed_var,\n+\t\t\t\t     const char *str_val,\n+\t\t\t\t     const struct config_context *ctx,\n+\t\t\t\t     void *cb UNUSED) {\n+\tconst char *config_prefix = \"repo.structure.concern.\";\n+\tif (!strncmp(config_prefix, prefixed_var, strlen(config_prefix))) {\n+\t\tconst char *concern_name = &prefixed_var[strlen(config_prefix)];\n+\t\tfor (int i = 0; concern_stat_thresholds[i].name != 0; i++) {\n+\t\t\tif (!strcmp(concern_stat_thresholds[i].name, concern_name)) {\n+\t\t\t\tint value = git_config_int(prefixed_var, str_val, ctx->kvi);\n+\t\t\t\tif (value < 0)\n+\t\t\t\t\treturn -1;\n+\t\t\t\tconcern_stat_thresholds[i].threshold = value;\n+\t\t\t\treturn 0;\n+\t\t\t}\n+\t\t}\n+\t\twarning(_(\"Invalid concern threshold name: '%s'\"), concern_name);\n+\t\treturn 0;\n+\t}\n+\treturn 0;\n+}\n+\n+static size_t approx_log2(long value) {\n+\tint position = 0;\n+\tif (value <= 0) {\n+\t\treturn 0;\n+\t}\n+\twhile (value > 1) {\n+\t\tposition++;\n+\t\tvalue >>= 1;\n+\t}\n+\treturn position;\n+}\n+\n+static size_t repo_structure_concern_level(enum concern_stat stat_id,\n+\t\t\t\t\t   size_t value) {\n+\tsize_t log_value;\n+\tif (stat_id >= ARRAY_SIZE(concern_stat_thresholds))\n+\t\tBUG(\"unexpected repository stat type\");\n+\tlog_value = approx_log2(value);\n+\tif (log_value > concern_stat_thresholds[stat_id].threshold)\n+\t\treturn log_value -\n+\t\t\tconcern_stat_thresholds[stat_id].threshold;\n+\telse\n+\t\treturn 0;\n+}\n+\n /*\n  * Holds column data that gets stored for each row.\n  */\n@@ -358,6 +468,7 @@ struct stats_table_entry {\n \tconst char *unit;\n \tsize_t index;\n \tstruct object_id *oid;\n+\tsize_t concern;\n };\n \n static void stats_table_vaddf(struct stats_table *table,\n@@ -391,6 +502,8 @@ static void stats_table_vaddf(struct stats_table *table,\n \t\tif (value_width > table->value_col_width)\n \t\t\ttable->value_col_width = value_width;\n \t}\n+\tif ((int)entry->concern > table->concern_col_width)\n+\t\ttable->concern_col_width = entry->concern;\n \tif (entry->unit) {\n \t\tint unit_width = utf8_strwidth(entry->unit);\n \t\tif (unit_width > table->unit_col_width)\n@@ -410,13 +523,14 @@ static void stats_table_addf(struct stats_table *table, const char *format, ...)\n }\n \n static void stats_table_count_addf(struct stats_table *table, size_t value,\n-\t\t\t\t   const char *format, ...)\n+\t\t\t\t   size_t concern, const char *format, ...)\n {\n \tstruct stats_table_entry *entry;\n \tva_list ap;\n \n \tCALLOC_ARRAY(entry, 1);\n \thumanise_count(value, &entry->value, &entry->unit);\n+\tentry->concern = concern;\n \n \tva_start(ap, format);\n \tstats_table_vaddf(table, entry, format, ap);\n@@ -425,13 +539,14 @@ static void stats_table_count_addf(struct stats_table *table, size_t value,\n \n static void stats_table_object_count_addf(struct stats_table *table,\n \t\t\t\t\t  struct object_id *oid, size_t value,\n-\t\t\t\t\t  const char *format, ...)\n+\t\t\t\t\t  size_t concern, const char *format, ...)\n {\n \tstruct stats_table_entry *entry;\n \tva_list ap;\n \n \tCALLOC_ARRAY(entry, 1);\n \thumanise_count(value, &entry->value, &entry->unit);\n+\tentry->concern = concern;\n \n \t/*\n \t * A NULL OID should not have a table annotation.\n@@ -445,6 +560,7 @@ static void stats_table_object_count_addf(struct stats_table *table,\n }\n \n static void stats_table_size_addf(struct stats_table *table, size_t value,\n+\t\t\t\t  size_t concern,\n \t\t\t\t  const char *format, ...)\n {\n \tstruct stats_table_entry *entry;\n@@ -452,6 +568,7 @@ static void stats_table_size_addf(struct stats_table *table, size_t value,\n \n \tCALLOC_ARRAY(entry, 1);\n \thumanise_bytes(value, &entry->value, &entry->unit, HUMANISE_COMPACT);\n+\tentry->concern = concern;\n \n \tva_start(ap, format);\n \tstats_table_vaddf(table, entry, format, ap);\n@@ -460,6 +577,7 @@ static void stats_table_size_addf(struct stats_table *table, size_t value,\n \n static void stats_table_object_size_addf(struct stats_table *table,\n \t\t\t\t\t struct object_id *oid, size_t value,\n+\t\t\t\t\t size_t concern,\n \t\t\t\t\t const char *format, ...)\n {\n \tstruct stats_table_entry *entry;\n@@ -467,6 +585,7 @@ static void stats_table_object_size_addf(struct stats_table *table,\n \n \tCALLOC_ARRAY(entry, 1);\n \thumanise_bytes(value, &entry->value, &entry->unit, HUMANISE_COMPACT);\n+\tentry->concern = concern;\n \n \t/*\n \t * A NULL OID should not have a table annotation.\n@@ -501,47 +620,92 @@ static void stats_table_setup_structure(struct stats_table *table,\n \n \tref_total = get_total_reference_count(refs);\n \tstats_table_addf(table, \"* %s\", _(\"References\"));\n-\tstats_table_count_addf(table, ref_total, \"  * %s\", _(\"Count\"));\n-\tstats_table_count_addf(table, refs->branches, \"    * %s\", _(\"Branches\"));\n-\tstats_table_count_addf(table, refs->tags, \"    * %s\", _(\"Tags\"));\n-\tstats_table_count_addf(table, refs->remotes, \"    * %s\", _(\"Remotes\"));\n-\tstats_table_count_addf(table, refs->others, \"    * %s\", _(\"Others\"));\n+\tstats_table_count_addf(table, ref_total, 0,\n+\t\t\t       \"  * %s\", _(\"Count\"));\n+\tstats_table_count_addf(table, refs->branches, 0,\n+\t\t\t       \"    * %s\", _(\"Branches\"));\n+\tstats_table_count_addf(table, refs->tags,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_TAG_COUNT,\n+\t\t\t\t       refs->tags),\n+\t\t\t       \"    * %s\", _(\"Tags\"));\n+\tstats_table_count_addf(table, refs->remotes, 0,\n+\t\t\t       \"    * %s\", _(\"Remotes\"));\n+\tstats_table_count_addf(table, refs->others, 0,\n+\t\t\t       \"    * %s\", _(\"Others\"));\n \n \tobject_count_total = get_total_object_values(&objects->type_counts);\n \tstats_table_addf(table, \"\");\n \tstats_table_addf(table, \"* %s\", _(\"Reachable objects\"));\n-\tstats_table_count_addf(table, object_count_total, \"  * %s\", _(\"Count\"));\n+\tstats_table_count_addf(table, object_count_total, 0,\n+\t\t\t       \"  * %s\", _(\"Count\"));\n \tstats_table_count_addf(table, objects->type_counts.commits,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_COMMIT_COUNT,\n+\t\t\t\t       objects->type_counts.commits),\n \t\t\t       \"    * %s\", _(\"Commits\"));\n \tstats_table_count_addf(table, objects->type_counts.trees,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_TREE_COUNT,\n+\t\t\t\t       objects->type_counts.trees),\n \t\t\t       \"    * %s\", _(\"Trees\"));\n \tstats_table_count_addf(table, objects->type_counts.blobs,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_BLOB_COUNT,\n+\t\t\t\t       objects->type_counts.blobs),\n \t\t\t       \"    * %s\", _(\"Blobs\"));\n \tstats_table_count_addf(table, objects->type_counts.tags,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_TAG_COUNT,\n+\t\t\t\t       objects->type_counts.tags),\n \t\t\t       \"    * %s\", _(\"Tags\"));\n \n \tinflated_object_total = get_total_object_values(&objects->inflated_sizes);\n-\tstats_table_size_addf(table, inflated_object_total,\n+\tstats_table_size_addf(table, inflated_object_total, 0,\n \t\t\t      \"  * %s\", _(\"Inflated size\"));\n \tstats_table_size_addf(table, objects->inflated_sizes.commits,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_COMMIT_INFLATED_SIZE,\n+\t\t\t\t      objects->inflated_sizes.commits),\n \t\t\t      \"    * %s\", _(\"Commits\"));\n \tstats_table_size_addf(table, objects->inflated_sizes.trees,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_TREE_INFLATED_SIZE,\n+\t\t\t\t      objects->inflated_sizes.trees),\n \t\t\t      \"    * %s\", _(\"Trees\"));\n \tstats_table_size_addf(table, objects->inflated_sizes.blobs,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_BLOB_INFLATED_SIZE,\n+\t\t\t\t      objects->inflated_sizes.blobs),\n \t\t\t      \"    * %s\", _(\"Blobs\"));\n \tstats_table_size_addf(table, objects->inflated_sizes.tags,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_TAG_INFLATED_SIZE,\n+\t\t\t\t      objects->inflated_sizes.tags),\n \t\t\t      \"    * %s\", _(\"Tags\"));\n \n \tdisk_object_total = get_total_object_values(&objects->disk_sizes);\n-\tstats_table_size_addf(table, disk_object_total,\n+\tstats_table_size_addf(table, disk_object_total, 0,\n \t\t\t      \"  * %s\", _(\"Disk size\"));\n \tstats_table_size_addf(table, objects->disk_sizes.commits,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_COMMIT_SIZE,\n+\t\t\t\t      objects->disk_sizes.commits),\n \t\t\t      \"    * %s\", _(\"Commits\"));\n \tstats_table_size_addf(table, objects->disk_sizes.trees,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_TREE_SIZE,\n+\t\t\t\t      objects->disk_sizes.trees),\n \t\t\t      \"    * %s\", _(\"Trees\"));\n \tstats_table_size_addf(table, objects->disk_sizes.blobs,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_BLOB_SIZE,\n+\t\t\t\t      objects->disk_sizes.blobs),\n \t\t\t      \"    * %s\", _(\"Blobs\"));\n \tstats_table_size_addf(table, objects->disk_sizes.tags,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_TAG_SIZE,\n+\t\t\t\t      objects->disk_sizes.tags),\n \t\t\t      \"    * %s\", _(\"Tags\"));\n \n \tstats_table_addf(table, \"\");\n@@ -550,51 +714,71 @@ static void stats_table_setup_structure(struct stats_table *table,\n \tstats_table_object_size_addf(table,\n \t\t\t\t     &objects->largest.commit_size.oid,\n \t\t\t\t     objects->largest.commit_size.value,\n+\t\t\t\t     repo_structure_concern_level(\n+\t\t\t\t\t     CONCERN_STAT_COMMIT_LARGEST,\n+\t\t\t\t\t     objects->largest.commit_size.value),\n \t\t\t\t     \"    * %s\", _(\"Maximum size\"));\n \tstats_table_object_count_addf(table,\n \t\t\t\t      &objects->largest.parent_count.oid,\n \t\t\t\t      objects->largest.parent_count.value,\n+\t\t\t\t      0,\n \t\t\t\t      \"    * %s\", _(\"Maximum parents\"));\n \tstats_table_addf(table, \"  * %s\", _(\"Trees\"));\n \tstats_table_object_size_addf(table,\n \t\t\t\t     &objects->largest.tree_size.oid,\n \t\t\t\t     objects->largest.tree_size.value,\n+\t\t\t\t      repo_structure_concern_level(\n+\t\t\t\t\t      CONCERN_STAT_TREE_LARGEST,\n+\t\t\t\t\t      objects->largest.tree_size.value),\n \t\t\t\t     \"    * %s\", _(\"Maximum size\"));\n \tstats_table_object_count_addf(table,\n \t\t\t\t      &objects->largest.tree_entries.oid,\n \t\t\t\t      objects->largest.tree_entries.value,\n+\t\t\t\t      0,\n \t\t\t\t      \"    * %s\", _(\"Maximum entries\"));\n \tstats_table_addf(table, \"  * %s\", _(\"Blobs\"));\n \tstats_table_object_size_addf(table,\n \t\t\t\t     &objects->largest.blob_size.oid,\n \t\t\t\t     objects->largest.blob_size.value,\n+\t\t\t\t     repo_structure_concern_level(\n+\t\t\t\t\t     CONCERN_STAT_BLOB_LARGEST,\n+\t\t\t\t\t     objects->largest.blob_size.value),\n \t\t\t\t     \"    * %s\", _(\"Maximum size\"));\n \tstats_table_addf(table, \"  * %s\", _(\"Tags\"));\n \tstats_table_object_size_addf(table,\n \t\t\t\t     &objects->largest.tag_size.oid,\n \t\t\t\t     objects->largest.tag_size.value,\n+\t\t\t\t     repo_structure_concern_level(\n+\t\t\t\t\t     CONCERN_STAT_TAG_LARGEST,\n+\t\t\t\t\t     objects->largest.tag_size.value),\n \t\t\t\t     \"    * %s\", _(\"Maximum size\"));\n }\n \n #define INDEX_WIDTH 4\n \n-static void stats_table_print_structure(const struct stats_table *table)\n+static void stats_table_print_structure(const struct stats_table *table,\n+\t\t\t\t\tbool show_concern)\n {\n \tconst char *name_col_title = _(\"Repository structure\");\n \tconst char *value_col_title = _(\"Value\");\n+\tconst char *concern_col_title = _(\"Concern\");\n \tint title_name_width = utf8_strwidth(name_col_title);\n \tint title_value_width = utf8_strwidth(value_col_title);\n+\tint title_concern_width = utf8_strwidth(concern_col_title);\n \tint name_col_width = table->name_col_width;\n \tint value_col_width = table->value_col_width;\n \tint unit_col_width = table->unit_col_width;\n+\tint concern_col_width = table->concern_col_width;\n \tstruct string_list_item *item;\n \tstruct strbuf buf = STRBUF_INIT;\n+\tstruct strbuf concern_str = STRBUF_INIT;\n \n \tif (title_name_width > name_col_width)\n \t\tname_col_width = title_name_width;\n \tif (title_value_width > value_col_width + unit_col_width + 1)\n \t\tvalue_col_width = title_value_width - unit_col_width;\n-\n+\tif (show_concern && title_concern_width > concern_col_width)\n+\t\tconcern_col_width = title_concern_width;\n \tstrbuf_addstr(&buf, \"| \");\n \tstrbuf_utf8_align(&buf, ALIGN_LEFT, name_col_width + INDEX_WIDTH,\n \t\t\t  name_col_title);\n@@ -602,6 +786,12 @@ static void stats_table_print_structure(const struct stats_table *table)\n \tstrbuf_utf8_align(&buf, ALIGN_LEFT,\n \t\t\t  value_col_width + unit_col_width + 1, value_col_title);\n \tstrbuf_addstr(&buf, \" |\");\n+\tif (show_concern) {\n+\t\tstrbuf_addch(&buf, ' ');\n+\t\tstrbuf_utf8_align(&buf, ALIGN_LEFT, concern_col_width,\n+\t\t\t  concern_col_title);\n+\t\tstrbuf_addstr(&buf, \" |\");\n+\t}\n \tprintf(\"%s\\n\", buf.buf);\n \n \tprintf(\"| \");\n@@ -610,17 +800,28 @@ static void stats_table_print_structure(const struct stats_table *table)\n \tprintf(\" | \");\n \tfor (int i = 0; i < value_col_width + unit_col_width + 1; i++)\n \t\tputchar('-');\n-\tprintf(\" |\\n\");\n+\tprintf(\" |\");\n+\tif (show_concern) {\n+\t\tputchar(' ');\n+\t\tfor (int i = 0; i < concern_col_width; i++) {\n+\t\t\tputchar('-');\n+\t\t}\n+\t\tprintf(\" |\");\n+\t}\n+\tputchar('\\n');\n \n \tfor_each_string_list_item(item, &table->rows) {\n \t\tstruct stats_table_entry *entry = item->util;\n \t\tconst char *value = \"\";\n \t\tconst char *unit = \"\";\n+\t\tstrbuf_reset(&concern_str);\n \n \t\tif (entry) {\n \t\t\tvalue = entry->value;\n \t\t\tif (entry->unit)\n \t\t\t\tunit = entry->unit;\n+\t\t\tif (entry->concern > 0)\n+\t\t\t\tstrbuf_addchars(&concern_str, '*', entry->concern);\n \t\t}\n \n \t\tstrbuf_reset(&buf);\n@@ -638,6 +839,11 @@ static void stats_table_print_structure(const struct stats_table *table)\n \t\tstrbuf_addch(&buf, ' ');\n \t\tstrbuf_utf8_align(&buf, ALIGN_LEFT, unit_col_width, unit);\n \t\tstrbuf_addstr(&buf, \" |\");\n+\t\tif (show_concern) {\n+\t\t\tstrbuf_addch(&buf, ' ');\n+\t\t\tstrbuf_utf8_align(&buf, ALIGN_LEFT, concern_col_width, concern_str.buf);\n+\t\t\tstrbuf_addstr(&buf, \" |\");\n+\t\t}\n \t\tprintf(\"%s\\n\", buf.buf);\n \t}\n \n@@ -681,12 +887,15 @@ static void print_object_data(const char *key, char key_delim,\n }\n \n static void structure_keyvalue_print(struct repo_structure *stats,\n-\t\t\t\t     char key_delim, char value_delim)\n+\t\t\t\t     char key_delim, char value_delim,\n+\t\t\t\t     bool show_concern)\n {\n \tprint_keyvalue(\"references.branches.count\", key_delim,\n \t\t       stats->refs.branches, value_delim);\n+\n \tprint_keyvalue(\"references.tags.count\", key_delim,\n \t\t       stats->refs.tags, value_delim);\n+\n \tprint_keyvalue(\"references.remotes.count\", key_delim,\n \t\t       stats->refs.remotes, value_delim);\n \tprint_keyvalue(\"references.others.count\", key_delim,\n@@ -694,39 +903,120 @@ static void structure_keyvalue_print(struct repo_structure *stats,\n \n \tprint_keyvalue(\"objects.commits.count\", key_delim,\n \t\t       stats->objects.type_counts.commits, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.commits.count.concern_level\", key_delim,\n+\t\t\t      repo_structure_concern_level(CONCERN_STAT_COMMIT_COUNT,\n+\t\t\t\t\t\t\t   stats->objects.type_counts.commits),\n+\t\t\t      value_delim);\n \tprint_keyvalue(\"objects.trees.count\", key_delim,\n \t\t       stats->objects.type_counts.trees, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.trees.count.concern_level\", key_delim,\n+\t\t\t      repo_structure_concern_level(CONCERN_STAT_TREE_COUNT,\n+\t\t\t\t\t\t\t   stats->objects.type_counts.trees),\n+\t\t\t      value_delim);\n \tprint_keyvalue(\"objects.blobs.count\", key_delim,\n \t\t       stats->objects.type_counts.blobs, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.blobs.count.concern_level\", key_delim,\n+\t\t\t      repo_structure_concern_level(CONCERN_STAT_BLOB_COUNT,\n+\t\t\t\t\t\t\t   stats->objects.type_counts.blobs),\n+\t\t\t      value_delim);\n \tprint_keyvalue(\"objects.tags.count\", key_delim,\n \t\t       stats->objects.type_counts.tags, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.tags.count.concern_level\", key_delim,\n+\t\t\t      repo_structure_concern_level(CONCERN_STAT_TAG_COUNT,\n+\t\t\t\t\t\t\t   stats->objects.type_counts.tags),\n+\t\t\t      value_delim);\n \n \tprint_keyvalue(\"objects.commits.inflated_size\", key_delim,\n \t\t       stats->objects.inflated_sizes.commits, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.commits.inflated_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_COMMIT_INFLATED_SIZE,\n+\t\t\t\t\t\t\t    stats->objects.inflated_sizes.commits),\n+\t\t\t      value_delim);\n \tprint_keyvalue(\"objects.trees.inflated_size\", key_delim,\n \t\t       stats->objects.inflated_sizes.trees, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.trees.inflated_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_TREE_INFLATED_SIZE,\n+\t\t\t\t\t\t\t    stats->objects.inflated_sizes.trees),\n+\t\t\t      value_delim);\n+\n \tprint_keyvalue(\"objects.blobs.inflated_size\", key_delim,\n \t\t       stats->objects.inflated_sizes.blobs, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.blobs.inflated_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_BLOB_INFLATED_SIZE,\n+\t\t\t\t\t\t\t    stats->objects.inflated_sizes.blobs),\n+\t\t\t      value_delim);\n \tprint_keyvalue(\"objects.tags.inflated_size\", key_delim,\n \t\t       stats->objects.inflated_sizes.tags, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.tags.inflated_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_TAG_INFLATED_SIZE,\n+\t\t\t\t\t\t\t    stats->objects.inflated_sizes.tags),\n+\t\t\t      value_delim);\n \n \tprint_keyvalue(\"objects.commits.disk_size\", key_delim,\n \t\t       stats->objects.disk_sizes.commits, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.commits.disk_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_COMMIT_SIZE,\n+\t\t\t\t\t\t\t    stats->objects.disk_sizes.commits),\n+\t\t\t      value_delim);\n \tprint_keyvalue(\"objects.trees.disk_size\", key_delim,\n \t\t       stats->objects.disk_sizes.trees, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.trees.disk_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_TREE_SIZE,\n+\t\t\t\t\t\t\t    stats->objects.disk_sizes.trees),\n+\t\t\t      value_delim);\n \tprint_keyvalue(\"objects.blobs.disk_size\", key_delim,\n \t\t       stats->objects.disk_sizes.blobs, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.blobs.blob_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_BLOB_SIZE,\n+\t\t\t\t\t\t\t    stats->objects.disk_sizes.blobs),\n+\t\t\t      value_delim);\n \tprint_keyvalue(\"objects.tags.disk_size\", key_delim,\n \t\t       stats->objects.disk_sizes.tags, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.tags.disk_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_TAG_SIZE,\n+\t\t\t\t\t\t\t    stats->objects.disk_sizes.tags),\n+\t\t\t      value_delim);\n \n \tprint_object_data(\"objects.commits.max_size\", key_delim,\n \t\t\t  &stats->objects.largest.commit_size, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.commits.max_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_COMMIT_LARGEST,\n+\t\t\t\t\t\t\t    stats->objects.largest.commit_size.value),\n+\t\t\t      value_delim);\n \tprint_object_data(\"objects.trees.max_size\", key_delim,\n \t\t\t  &stats->objects.largest.tree_size, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.trees.max_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_TREE_LARGEST,\n+\t\t\t\t\t\t\t    stats->objects.largest.tree_size.value),\n+\t\t\t      value_delim);\n \tprint_object_data(\"objects.blobs.max_size\", key_delim,\n \t\t\t  &stats->objects.largest.blob_size, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.blobs.max_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_BLOB_LARGEST,\n+\t\t\t\t\t\t\t    stats->objects.largest.blob_size.value),\n+\t\t\t      value_delim);\n \tprint_object_data(\"objects.tags.max_size\", key_delim,\n \t\t\t  &stats->objects.largest.tag_size, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.tags.max_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_TAG_LARGEST,\n+\t\t\t\t\t\t\t    stats->objects.largest.tag_size.value),\n+\t\t\t      value_delim);\n \n \tprint_object_data(\"objects.commits.max_parents\", key_delim,\n \t\t\t  &stats->objects.largest.parent_count, value_delim);\n@@ -935,6 +1225,8 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,\n \tstruct repo_structure stats = { 0 };\n \tstruct rev_info revs;\n \tint show_progress = -1;\n+\tint show_concern = 0;\n+\n \tstruct option options[] = {\n \t\tOPT_CALLBACK_F(0, \"format\", &format, N_(\"format\"),\n \t\t\t       N_(\"output format\"),\n@@ -944,6 +1236,7 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,\n \t\t\t       PARSE_OPT_NONEG | PARSE_OPT_NOARG,\n \t\t\t       parse_format_cb),\n \t\tOPT_BOOL(0, \"progress\", &show_progress, N_(\"show progress\")),\n+\t\tOPT_BOOL(0, \"concern\", &show_concern, N_(\"show level of concern\")),\n \t\tOPT_END()\n \t};\n \n@@ -962,13 +1255,13 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,\n \tswitch (format) {\n \tcase FORMAT_TABLE:\n \t\tstats_table_setup_structure(&table, &stats);\n-\t\tstats_table_print_structure(&table);\n+\t\tstats_table_print_structure(&table, show_concern);\n \t\tbreak;\n \tcase FORMAT_NEWLINE_TERMINATED:\n-\t\tstructure_keyvalue_print(&stats, '=', '\\n');\n+\t\tstructure_keyvalue_print(&stats, '=', '\\n', show_concern);\n \t\tbreak;\n \tcase FORMAT_NUL_TERMINATED:\n-\t\tstructure_keyvalue_print(&stats, '\\n', '\\0');\n+\t\tstructure_keyvalue_print(&stats, '\\n', '\\0', show_concern);\n \t\tbreak;\n \tdefault:\n \t\tBUG(\"invalid output format\");\ndiff --git a/sideband.c b/sideband.c\nindex 374187402e..0e75ab2317 100644\n--- a/sideband.c\n+++ b/sideband.c\n@@ -1,4 +1,4 @@\n-#define USE_THE_REPOSITORY_VARIABLE\n+define USE_THE_REPOSITORY_VARIABLE\n #define DISABLE_SIGN_COMPARE_WARNINGS\n \n #include \"git-compat-util.h\"\ndiff --git a/t/t1901-repo-structure.sh b/t/t1901-repo-structure.sh\nindex 02cc2b594a..f1e3fd2a75 100755\n--- a/t/t1901-repo-structure.sh\n+++ b/t/t1901-repo-structure.sh\n@@ -67,77 +67,7 @@ test_expect_success 'empty repository' '\n \t\tEOF\n \n \t\tgit repo structure >out 2>err &&\n-\n-\t\ttest_cmp expect out &&\n-\t\ttest_line_count = 0 err\n-\t)\n-'\n-\n-test_expect_success SHA1 'repository with references and objects' '\n-\ttest_when_finished \"rm -rf repo\" &&\n-\tgit init repo &&\n-\t(\n-\t\tcd repo &&\n-\t\ttest_commit_bulk 1005 &&\n-\t\tgit tag -a foo -m bar &&\n-\n-\t\toid=\"$(git rev-parse HEAD)\" &&\n-\t\tgit update-ref refs/remotes/origin/foo \"$oid\" &&\n-\n-\t\t# Also creates a commit, tree, and blob.\n-\t\tgit notes add -m foo &&\n-\n-\t\t# The tags disk size is handled specially due to the\n-\t\t# git-rev-list(1) --disk-usage=human option printing the full\n-\t\t# \"byte/bytes\" unit string instead of just \"B\".\n-\t\tcat >expect <<-EOF &&\n-\t\t| Repository structure      | Value      |\n-\t\t| ------------------------- | ---------- |\n-\t\t| * References              |            |\n-\t\t|   * Count                 |      4     |\n-\t\t|     * Branches            |      1     |\n-\t\t|     * Tags                |      1     |\n-\t\t|     * Remotes             |      1     |\n-\t\t|     * Others              |      1     |\n-\t\t|                           |            |\n-\t\t| * Reachable objects       |            |\n-\t\t|   * Count                 |   3.02 k   |\n-\t\t|     * Commits             |   1.01 k   |\n-\t\t|     * Trees               |   1.01 k   |\n-\t\t|     * Blobs               |   1.01 k   |\n-\t\t|     * Tags                |      1     |\n-\t\t|   * Inflated size         |  16.03 MiB |\n-\t\t|     * Commits             | 217.92 KiB |\n-\t\t|     * Trees               |  15.81 MiB |\n-\t\t|     * Blobs               |  11.68 KiB |\n-\t\t|     * Tags                |    132 B   |\n-\t\t|   * Disk size             | $(object_type_disk_usage all true) |\n-\t\t|     * Commits             | $(object_type_disk_usage commit true) |\n-\t\t|     * Trees               | $(object_type_disk_usage tree true) |\n-\t\t|     * Blobs               |  $(object_type_disk_usage blob true) |\n-\t\t|     * Tags                |    $(object_type_disk_usage tag) B   |\n-\t\t|                           |            |\n-\t\t| * Largest objects         |            |\n-\t\t|   * Commits               |            |\n-\t\t|     * Maximum size    [1] |    223 B   |\n-\t\t|     * Maximum parents [2] |      1     |\n-\t\t|   * Trees                 |            |\n-\t\t|     * Maximum size    [3] |  32.29 KiB |\n-\t\t|     * Maximum entries [4] |   1.01 k   |\n-\t\t|   * Blobs                 |            |\n-\t\t|     * Maximum size    [5] |     13 B   |\n-\t\t|   * Tags                  |            |\n-\t\t|     * Maximum size    [6] |    132 B   |\n-\n-\t\t[1] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a\n-\t\t[2] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a\n-\t\t[3] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c\n-\t\t[4] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c\n-\t\t[5] 97d808e45116bf02103490294d3d46dad7a2ac62\n-\t\t[6] 4dae4f5954f5e6feb3577cfb1b181daa3fd3afd2\n-\t\tEOF\n-\n-\t\tgit repo structure >out 2>err &&\n+\t\tcp out /tmp/test1-out &&\n \n \t\ttest_cmp expect out &&\n \t\ttest_line_count = 0 err\n@@ -204,6 +134,59 @@ test_expect_success SHA1 'lines and nul format' '\n \t)\n '\n \n+test_expect_success SHA1 'lines format with concerns' '\n+\ttest_when_finished \"rm -rf repo\" &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\t# configure thresholds to get concern indications\n+\t\tgit config repo.structure.concern.commit-count 7 &&\n+\t\tgit config repo.structure.concern.commit-size 14 &&\n+\t\tgit config repo.structure.concern.tree-size 10 &&\n+\t\tgit config repo.structure.concern.blob-size 8 &&\n+\t\tgit config repo.structure.concern.blob-inflated-size 10 &&\n+\t\tgit config repo.structure.concern.tag-size 2 &&\n+\t\tgit config repo.structure.concern.commit-largest 4 &&\n+\t\tgit config repo.structure.concern.blob-largest 2 &&\n+\t\ttouch configured &&\n+\t\ttest_commit_bulk 1005 &&\n+\t\tgit tag -a foo -m bar &&\n+\t\toid=\"$(git rev-parse HEAD)\" &&\n+\t\tgit update-ref refs/remotes/origin/foo \"$oid\" &&\n+\n+\t\t# Also creates a commit, tree, and blob.\n+\t\tgit notes add -m foo &&\n+\n+\n+\t\t# The tags disk size is handled specially due to the\n+\t\t# git-rev-list(1) --disk-usage=human option printing the full\n+\t\t# \"byte/bytes\" unit string instead of just \"B\".\n+\t\tcat >expect <<-EOF &&\n+\t\tobjects.commits.count.concern_level=2\n+\t\tobjects.trees.count.concern_level=0\n+\t\tobjects.blobs.count.concern_level=0\n+\t\tobjects.tags.count.concern_level=0\n+\t\tobjects.commits.inflated_size.concern_level=0\n+\t\tobjects.trees.inflated_size.concern_level=0\n+\t\tobjects.blobs.inflated_size.concern_level=3\n+\t\tobjects.tags.inflated_size.concern_level=0\n+\t\tobjects.commits.disk_size.concern_level=3\n+\t\tobjects.trees.disk_size.concern_level=8\n+\t\tobjects.blobs.blob_size.concern_level=6\n+\t\tobjects.tags.disk_size.concern_level=4\n+\t\tobjects.commits.max_size.concern_level=3\n+\t\tobjects.trees.max_size.concern_level=0\n+\t\tobjects.blobs.max_size.concern_level=1\n+\t\tobjects.tags.max_size.concern_level=0\n+\t\tEOF\n+\n+\t\tgit repo structure --concern --format=lines >actual 2>err &&\n+\t\ttest_grep \"concern_level\" actual >actual-concerns &&\n+\t\ttest_cmp expect actual-concerns &&\n+\t\ttest_line_count = 0 err\n+\t)\n+'\n+\n test_expect_success 'progress meter option' '\n \ttest_when_finished \"rm -rf repo\" &&\n \tgit init repo &&\n\n-- \n2.53.0\n\n\n"},{"id":"556383","messageId":"20261026-b4-repo-structure-concern-v2-2-e0df34228dc8@fastmail.com","threadId":"66621","inReplyTo":"20261026-b4-repo-structure-concern-v2-0-e0df34228dc8@fastmail.com","subject":"[PATCH v2 2/3] repo: make concern thresholds configurable","fromName":"Mark C. Chu-Carroll","fromEmail":"markchucarroll@fastmail.com","sentAt":"2026-10-26T17:43:55Z","receivedAt":"2026-10-26T17:43:55Z","isPatch":true,"sender":{"key":"markchucarroll@fastmail.com","avatar":null},"body":"The concern reporting mechanism so far provides a\nmeasure of concern based on heuristic values estimated\nby analyzing a collection of (hopefully) representative\nrepositories. But for different projects/usecases, the\nthresholds at which a value becomes concerning may vary.\n\nProvide a mechanism using git config to allow users to\nconfigure per-repo concern levels for \"git repo structure\"\nconcern reporting.\n---\n builtin/repo.c            |  62 +++++++++-------\n sideband.c                |   2 +-\n t/t1901-repo-structure.sh | 180 ++++++++++++++++++++++++++++++++++++++++++----\n 3 files changed, 203 insertions(+), 41 deletions(-)\n\ndiff --git a/builtin/repo.c b/builtin/repo.c\nindex a450497d4e..ed579e6685 100644\n--- a/builtin/repo.c\n+++ b/builtin/repo.c\n@@ -2,6 +2,7 @@\n \n #include \"builtin.h\"\n #include \"commit.h\"\n+#include \"config.h\"\n #include \"environment.h\"\n #include \"hash.h\"\n #include \"hex.h\"\n@@ -29,6 +30,8 @@\n #define REPO_STRUCTURE_USAGE \\\n \t\"git repo structure [--format=(table|lines|nul) | -z] [--[no-]concern]\"\n \n+#define REPO_STRUCTURE_CONFIG_PREFIX \"repo.concern.\"\n+\n static const char *const repo_usage[] = {\n \tREPO_INFO_USAGE,\n \tREPO_STRUCTURE_USAGE,\n@@ -386,40 +389,43 @@ enum concern_stat {\n \tCONCERN_STAT_TREE_INFLATED_SIZE,\n \tCONCERN_STAT_BLOB_INFLATED_SIZE,\n \tCONCERN_STAT_TAG_INFLATED_SIZE,\n+\tCONCERN_STAT_MAXIMUM\n };\n \n-struct concern_stat_threshold repo_structure_concern_thresholds[] = {\n-\t{ .name = \"commit_count\", .threshold = 19 },\n-\t{.name = \"tree_count\", .threshold = 21 },\n-\t{ .name = \"blob_count\", .threshold = 22 },\n-\t{ .name = \"tag_count\", .threshold = 15 },\n-\t{ .name = \"commit_size\", .threshold = 28 },\n-\t{ .name = \"tree_size\", .threshold = 27 },\n-\t{ .name = \"blob_size\", .threshold = 36 },\n-\t{ .name = \"tags_size\", .threshold = 18 },\n-\t{ .name = \"commit_largest\", .threshold = 18  },\n-\t{ .name = \"tree_largest\", .threshold = 18 },\n-\t{ .name = \"blob_largest\", .threshold = 26 },\n-\t{ .name = \"tag_largest\", .threshold =  15 },\n-\t{ .name = \"commit_inflated_size\", .threshold = 28 },\n-\t{ .name = \"tree_inflated_size\", .threshold = 32 },\n-\t{ .name = \"blob_inflated_size\", .threshold = 34 },\n-\t{ .name = \"tag_inflated_size\", .threshold = 25}\n+struct concern_stat_threshold concern_stat_thresholds[] = {\n+\t{ .name = \"commit-count\", .threshold = 19 },\n+\t{ .name = \"tree-count\", .threshold = 21 },\n+\t{ .name = \"blob-count\", .threshold = 22 },\n+\t{ .name = \"tag-count\", .threshold = 15 },\n+\t{ .name = \"commit-size\", .threshold = 28 },\n+\t{ .name = \"tree-size\", .threshold = 27 },\n+\t{ .name = \"blob-size\", .threshold = 36 },\n+\t{ .name = \"tag-size\", .threshold = 18 },\n+\t{ .name = \"commit-largest\", .threshold = 18  },\n+\t{ .name = \"tree-largest\", .threshold = 18 },\n+\t{ .name = \"blob-largest\", .threshold = 26 },\n+\t{ .name = \"tag-largest\", .threshold =  15 },\n+\t{ .name = \"commit-inflated-size\", .threshold = 28 },\n+\t{ .name = \"tree-inflated-size\", .threshold = 32 },\n+\t{ .name = \"blob-inflated-size\", .threshold = 34 },\n+\t{ .name = \"tag-inflated-size\", .threshold = 25},\n+\t{ 0 }\n };\n \n /*\n  * Concern thresholds aren't universal. We try to set reasonable default\n  * values, but projects may want to set these thresholds differently. To\n  * support that, they can set the thresholds for their project in the\n- * project's git config, in the section \"repo.structure.concern\".\n+ * project's git config, in the section \"repo.concern\".\n   */\n static int config_concern_thresholds(const char *prefixed_var,\n \t\t\t\t     const char *str_val,\n \t\t\t\t     const struct config_context *ctx,\n \t\t\t\t     void *cb UNUSED) {\n-\tconst char *config_prefix = \"repo.structure.concern.\";\n-\tif (!strncmp(config_prefix, prefixed_var, strlen(config_prefix))) {\n-\t\tconst char *concern_name = &prefixed_var[strlen(config_prefix)];\n+\tif (!strncmp(REPO_STRUCTURE_CONFIG_PREFIX, prefixed_var,\n+\t      strlen(REPO_STRUCTURE_CONFIG_PREFIX))) {\n+\t\tconst char *concern_name =\n+\t\t\t&prefixed_var[strlen(REPO_STRUCTURE_CONFIG_PREFIX)];\n \t\tfor (int i = 0; concern_stat_thresholds[i].name != 0; i++) {\n \t\t\tif (!strcmp(concern_stat_thresholds[i].name, concern_name)) {\n \t\t\t\tint value = git_config_int(prefixed_var, str_val, ctx->kvi);\n@@ -435,7 +441,7 @@ static int config_concern_thresholds(const char *prefixed_var,\n \treturn 0;\n }\n \n-static size_t approx_log2(long value) {\n+static size_t approx_log2(size_t value) {\n \tint position = 0;\n \tif (value <= 0) {\n \t\treturn 0;\n@@ -450,7 +456,7 @@ static size_t approx_log2(long value) {\n static size_t repo_structure_concern_level(enum concern_stat stat_id,\n \t\t\t\t\t   size_t value) {\n \tsize_t log_value;\n-\tif (stat_id >= ARRAY_SIZE(concern_stat_thresholds))\n+\tif (stat_id >= CONCERN_STAT_MAXIMUM)\n \t\tBUG(\"unexpected repository stat type\");\n \tlog_value = approx_log2(value);\n \tif (log_value > concern_stat_thresholds[stat_id].threshold)\n@@ -844,6 +850,7 @@ static void stats_table_print_structure(const struct stats_table *table,\n \t\t\tstrbuf_utf8_align(&buf, ALIGN_LEFT, concern_col_width, concern_str.buf);\n \t\t\tstrbuf_addstr(&buf, \" |\");\n \t\t}\n+\t\tstrbuf_release(&concern_str);\n \t\tprintf(\"%s\\n\", buf.buf);\n \t}\n \n@@ -977,7 +984,7 @@ static void structure_keyvalue_print(struct repo_structure *stats,\n \tprint_keyvalue(\"objects.blobs.disk_size\", key_delim,\n \t\t       stats->objects.disk_sizes.blobs, value_delim);\n \tif (show_concern)\n-\t\tprint_keyvalue(\"objects.blobs.blob_size.concern_level\", key_delim,\n+\t\tprint_keyvalue(\"objects.blobs.disk_size.concern_level\", key_delim,\n \t\t\t       repo_structure_concern_level(CONCERN_STAT_BLOB_SIZE,\n \t\t\t\t\t\t\t    stats->objects.disk_sizes.blobs),\n \t\t\t      value_delim);\n@@ -997,7 +1004,7 @@ static void structure_keyvalue_print(struct repo_structure *stats,\n \t\t\t\t\t\t\t    stats->objects.largest.commit_size.value),\n \t\t\t      value_delim);\n \tprint_object_data(\"objects.trees.max_size\", key_delim,\n-\t\t\t  &stats->objects.largest.tree_size, value_delim);\n+\t\t\t &stats->objects.largest.tree_size, value_delim);\n \tif (show_concern)\n \t\tprint_keyvalue(\"objects.trees.max_size.concern_level\", key_delim,\n \t\t\t       repo_structure_concern_level(CONCERN_STAT_TREE_LARGEST,\n@@ -1011,7 +1018,7 @@ static void structure_keyvalue_print(struct repo_structure *stats,\n \t\t\t\t\t\t\t    stats->objects.largest.blob_size.value),\n \t\t\t      value_delim);\n \tprint_object_data(\"objects.tags.max_size\", key_delim,\n-\t\t\t  &stats->objects.largest.tag_size, value_delim);\n+\t\t\t   &stats->objects.largest.tag_size, value_delim);\n \tif (show_concern)\n \t\tprint_keyvalue(\"objects.tags.max_size.concern_level\", key_delim,\n \t\t\t       repo_structure_concern_level(CONCERN_STAT_TAG_LARGEST,\n@@ -1243,7 +1250,8 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,\n \targc = parse_options(argc, argv, prefix, options, repo_structure_usage, 0);\n \tif (argc)\n \t\tusage(_(\"too many arguments\"));\n-\n+\tif (show_concern)\n+\t\trepo_config(repo, &config_concern_thresholds, NULL);\n \trepo_init_revisions(repo, &revs, prefix);\n \n \tif (show_progress < 0)\ndiff --git a/sideband.c b/sideband.c\nindex 0e75ab2317..374187402e 100644\n--- a/sideband.c\n+++ b/sideband.c\n@@ -1,4 +1,4 @@\n-define USE_THE_REPOSITORY_VARIABLE\n+#define USE_THE_REPOSITORY_VARIABLE\n #define DISABLE_SIGN_COMPARE_WARNINGS\n \n #include \"git-compat-util.h\"\ndiff --git a/t/t1901-repo-structure.sh b/t/t1901-repo-structure.sh\nindex f1e3fd2a75..e8f1269ad4 100755\n--- a/t/t1901-repo-structure.sh\n+++ b/t/t1901-repo-structure.sh\n@@ -67,13 +67,166 @@ test_expect_success 'empty repository' '\n \t\tEOF\n \n \t\tgit repo structure >out 2>err &&\n-\t\tcp out /tmp/test1-out &&\n \n \t\ttest_cmp expect out &&\n \t\ttest_line_count = 0 err\n \t)\n '\n \n+test_expect_success SHA1 'repository with references and objects' '\n+\ttest_when_finished \"rm -rf repo\" &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\ttest_commit_bulk 1005 &&\n+\t\tgit tag -a foo -m bar &&\n+\n+\t\toid=\"$(git rev-parse HEAD)\" &&\n+\t\tgit update-ref refs/remotes/origin/foo \"$oid\" &&\n+\n+\t\t# Also creates a commit, tree, and blob.\n+\t\tgit notes add -m foo &&\n+\n+\t\t# The tags disk size is handled specially due to the\n+\t\t# git-rev-list(1) --disk-usage=human option printing the full\n+\t\t# \"byte/bytes\" unit string instead of just \"B\".\n+\t\tcat >expect <<-EOF &&\n+\t\t| Repository structure      | Value      |\n+\t\t| ------------------------- | ---------- |\n+\t\t| * References              |            |\n+\t\t|   * Count                 |      4     |\n+\t\t|     * Branches            |      1     |\n+\t\t|     * Tags                |      1     |\n+\t\t|     * Remotes             |      1     |\n+\t\t|     * Others              |      1     |\n+\t\t|                           |            |\n+\t\t| * Reachable objects       |            |\n+\t\t|   * Count                 |   3.02 k   |\n+\t\t|     * Commits             |   1.01 k   |\n+\t\t|     * Trees               |   1.01 k   |\n+\t\t|     * Blobs               |   1.01 k   |\n+\t\t|     * Tags                |      1     |\n+\t\t|   * Inflated size         |  16.03 MiB |\n+\t\t|     * Commits             | 217.92 KiB |\n+\t\t|     * Trees               |  15.81 MiB |\n+\t\t|     * Blobs               |  11.68 KiB |\n+\t\t|     * Tags                |    132 B   |\n+\t\t|   * Disk size             | $(object_type_disk_usage all true) |\n+\t\t|     * Commits             | $(object_type_disk_usage commit true) |\n+\t\t|     * Trees               | $(object_type_disk_usage tree true) |\n+\t\t|     * Blobs               |  $(object_type_disk_usage blob true) |\n+\t\t|     * Tags                |    $(object_type_disk_usage tag) B   |\n+\t\t|                           |            |\n+\t\t| * Largest objects         |            |\n+\t\t|   * Commits               |            |\n+\t\t|     * Maximum size    [1] |    223 B   |\n+\t\t|     * Maximum parents [2] |      1     |\n+\t\t|   * Trees                 |            |\n+\t\t|     * Maximum size    [3] |  32.29 KiB |\n+\t\t|     * Maximum entries [4] |   1.01 k   |\n+\t\t|   * Blobs                 |            |\n+\t\t|     * Maximum size    [5] |     13 B   |\n+\t\t|   * Tags                  |            |\n+\t\t|     * Maximum size    [6] |    132 B   |\n+\n+\t\t[1] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a\n+\t\t[2] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a\n+\t\t[3] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c\n+\t\t[4] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c\n+\t\t[5] 97d808e45116bf02103490294d3d46dad7a2ac62\n+\t\t[6] 4dae4f5954f5e6feb3577cfb1b181daa3fd3afd2\n+\t\tEOF\n+\n+\t\tgit repo structure >out 2>err &&\n+\n+\t\ttest_cmp expect out &&\n+\t\ttest_line_count = 0 err\n+\t)\n+'\n+\n+test_expect_success SHA1 'concern flagging' '\n+\ttest_when_finished \"rm -rf repo\" &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\t# differences between zlib and zlib-ng can cause this\n+\t\t# test to fail; so we just turn it off.\n+\t\tgit config core.compression 0 &&\n+\t\t# configure thresholds to get concern indications\n+\t\tgit config repo.concern.commit-count 7 &&\n+\t\tgit config repo.concern.commit-size 14 &&\n+\t\tgit config repo.concern.tree-size 10 &&\n+\t\tgit config repo.concern.blob-size 8 &&\n+\t\tgit config repo.concern.blob-inflated-size 10 &&\n+\t\tgit config repo.concern.tag-size 2 &&\n+\t\tgit config repo.concern.commit-largest 4 &&\n+\t\tgit config repo.concern.blob-largest 2 &&\n+\t\ttest_commit_bulk 1005 &&\n+\t\tgit tag -a foo -m bar &&\n+\t\toid=\"$(git rev-parse HEAD)\" &&\n+\t\tgit update-ref refs/remotes/origin/foo \"$oid\" &&\n+\n+\t\t# Also creates a commit, tree, and blob.\n+\t\tgit notes add -m foo &&\n+\n+\n+\t\t# The tags disk size is handled specially due to the\n+\t\t# git-rev-list(1) --disk-usage=human option printing the full\n+\t\t# \"byte/bytes\" unit string instead of just \"B\".\n+\t\tcat >expect <<-EOF &&\n+\t\t| Repository structure      | Value      | Concern  |\n+\t\t| ------------------------- | ---------- | -------- |\n+\t\t| * References              |            |          |\n+\t\t|   * Count                 |      4     |          |\n+\t\t|     * Branches            |      1     |          |\n+\t\t|     * Tags                |      1     |          |\n+\t\t|     * Remotes             |      1     |          |\n+\t\t|     * Others              |      1     |          |\n+\t\t|                           |            |          |\n+\t\t| * Reachable objects       |            |          |\n+\t\t|   * Count                 |   3.02 k   |          |\n+\t\t|     * Commits             |   1.01 k   | **       |\n+\t\t|     * Trees               |   1.01 k   |          |\n+\t\t|     * Blobs               |   1.01 k   |          |\n+\t\t|     * Tags                |      1     |          |\n+\t\t|   * Inflated size         |  16.03 MiB |          |\n+\t\t|     * Commits             | 217.92 KiB |          |\n+\t\t|     * Trees               |  15.81 MiB |          |\n+\t\t|     * Blobs               |  11.68 KiB | ***      |\n+\t\t|     * Tags                |    132 B   |          |\n+\t\t|   * Disk size             | $(object_type_disk_usage all true) |          |\n+\t\t|     * Commits             | $(object_type_disk_usage commit true) | ***      |\n+\t\t|     * Trees               | $(object_type_disk_usage tree true) | ******** |\n+\t\t|     * Blobs               |  $(object_type_disk_usage blob true) | ******   |\n+\t\t|     * Tags                |    $(object_type_disk_usage tag) B   | *****    |\n+\t\t|                           |            |          |\n+\t\t| * Largest objects         |            |          |\n+\t\t|   * Commits               |            |          |\n+\t\t|     * Maximum size    [1] |    223 B   | ***      |\n+\t\t|     * Maximum parents [2] |      1     |          |\n+\t\t|   * Trees                 |            |          |\n+\t\t|     * Maximum size    [3] |  32.29 KiB |          |\n+\t\t|     * Maximum entries [4] |   1.01 k   |          |\n+\t\t|   * Blobs                 |            |          |\n+\t\t|     * Maximum size    [5] |     13 B   | *        |\n+\t\t|   * Tags                  |            |          |\n+\t\t|     * Maximum size    [6] |    132 B   |          |\n+\n+\t\t[1] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a\n+\t\t[2] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a\n+\t\t[3] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c\n+\t\t[4] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c\n+\t\t[5] 97d808e45116bf02103490294d3d46dad7a2ac62\n+\t\t[6] 4dae4f5954f5e6feb3577cfb1b181daa3fd3afd2\n+\t\tEOF\n+\n+\t\tgit repo structure --concern >actual 2>err &&\n+\n+\t\ttest_cmp expect actual &&\n+\t\ttest_line_count = 0 err\n+\t)\n+'\n+\n test_expect_success SHA1 'lines and nul format' '\n \ttest_when_finished \"rm -rf repo\" &&\n \tgit init repo &&\n@@ -139,16 +292,18 @@ test_expect_success SHA1 'lines format with concerns' '\n \tgit init repo &&\n \t(\n \t\tcd repo &&\n+\t\t# differences between zlib and zlib-ng can cause this\n+\t\t# test to fail; so we just turn it off.\n+\t\tgit config core.compression 0 &&\n \t\t# configure thresholds to get concern indications\n-\t\tgit config repo.structure.concern.commit-count 7 &&\n-\t\tgit config repo.structure.concern.commit-size 14 &&\n-\t\tgit config repo.structure.concern.tree-size 10 &&\n-\t\tgit config repo.structure.concern.blob-size 8 &&\n-\t\tgit config repo.structure.concern.blob-inflated-size 10 &&\n-\t\tgit config repo.structure.concern.tag-size 2 &&\n-\t\tgit config repo.structure.concern.commit-largest 4 &&\n-\t\tgit config repo.structure.concern.blob-largest 2 &&\n-\t\ttouch configured &&\n+\t\tgit config repo.concern.commit-count 7 &&\n+\t\tgit config repo.concern.commit-size 14 &&\n+\t\tgit config repo.concern.tree-size 10 &&\n+\t\tgit config repo.concern.blob-size 8 &&\n+\t\tgit config repo.concern.blob-inflated-size 10 &&\n+\t\tgit config repo.concern.tag-size 2 &&\n+\t\tgit config repo.concern.commit-largest 4 &&\n+\t\tgit config repo.concern.blob-largest 2 &&\n \t\ttest_commit_bulk 1005 &&\n \t\tgit tag -a foo -m bar &&\n \t\toid=\"$(git rev-parse HEAD)\" &&\n@@ -157,7 +312,6 @@ test_expect_success SHA1 'lines format with concerns' '\n \t\t# Also creates a commit, tree, and blob.\n \t\tgit notes add -m foo &&\n \n-\n \t\t# The tags disk size is handled specially due to the\n \t\t# git-rev-list(1) --disk-usage=human option printing the full\n \t\t# \"byte/bytes\" unit string instead of just \"B\".\n@@ -172,8 +326,8 @@ test_expect_success SHA1 'lines format with concerns' '\n \t\tobjects.tags.inflated_size.concern_level=0\n \t\tobjects.commits.disk_size.concern_level=3\n \t\tobjects.trees.disk_size.concern_level=8\n-\t\tobjects.blobs.blob_size.concern_level=6\n-\t\tobjects.tags.disk_size.concern_level=4\n+\t\tobjects.blobs.disk_size.concern_level=6\n+\t\tobjects.tags.disk_size.concern_level=5\n \t\tobjects.commits.max_size.concern_level=3\n \t\tobjects.trees.max_size.concern_level=0\n \t\tobjects.blobs.max_size.concern_level=1\n\n-- \n2.53.0\n\n\n"},{"id":"556384","messageId":"20261026-b4-repo-structure-concern-v2-3-e0df34228dc8@fastmail.com","threadId":"66621","inReplyTo":"20261026-b4-repo-structure-concern-v2-0-e0df34228dc8@fastmail.com","subject":"[PATCH v2 3/3] repo: add documentation for level-of-concern.","fromName":"Mark C. Chu-Carroll","fromEmail":"markchucarroll@fastmail.com","sentAt":"2026-10-26T17:43:56Z","receivedAt":"2026-10-26T17:43:56Z","isPatch":true,"sender":{"key":"markchucarroll@fastmail.com","avatar":null},"body":"---\n Documentation/config/repo.adoc | 59 ++++++++++++++++++++++++++++++++++++++++++\n Documentation/git-repo.adoc    | 27 +++++++++++++++++--\n 2 files changed, 84 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/config/repo.adoc b/Documentation/config/repo.adoc\nnew file mode 100644\nindex 0000000000..811b099d72\n--- /dev/null\n+++ b/Documentation/config/repo.adoc\n@@ -0,0 +1,59 @@\n+`repo.concern.commit-count`::\n+\tSets the value of the concern threshold for commit count.\n+\n+`repo.concern.tree-count`::\n+\tSets the value of the concern threshold for tree count.\n+\n+`repo.concern.blob-count`::\n+\tSets the value of the concern threshold for blob count.\n+\n+`repo.concern.tag-count`::\n+\tSets the value of the concern threshold for tag count.\n+\n+`repo.concern.commit-size`::\n+\tSets the value of the concern threshold for total commit size.\n+\n+`repo.concern.tree-size`::\n+\tSets the value of the concern threshold for total tree size.\n+\n+`repo.concern.blob-size`::\n+\tSets the value of the concern threshold for total blob size.\n+\n+`repo.concern.tag-size`::\n+\tSets the value of the concern threshold for total tag size.\n+\n+`repo.concern.commit-inflated-size`::\n+\tSets the value of the concern threshold for total size\n+\tof all inflated commits.\n+\n+`repo.concern.tree-inflated-size`::\n+\tSets the value of the concern threshold for total size of\n+\tall inflated trees.\n+\n+`repo.concern.blob-inflated-size`::\n+\tSets the value of the concern threshold for total size of\n+\tall inflated blobs.\n+\n+`repo.concern.tag-inflated-size`::\n+\tSets the value of the concern threshold for the total size\n+\tof all inflated tags.\n+\n+`repo.concern.commit-largest`::\n+\tSets the value of the concern threshold the size of the largest\n+\tcommit.\n+\n+`repo.concern.tree-largest`::\n+\tSets the value of the concern threshold for the size of the\n+\tlargest tree.\n+\n+`repo.concern.blob-largest`::\n+\tSets the value of the concern threshold for the size of the\n+\tlargest blob.\n+\n+`repo.concern.tag-largest`::\n+\tSets the value of the concern threshold for the size of the\n+\tlargest tag.\n+\n+All of the concern thresholds are integer values for the\n+floor of the base-2 log of the threshold value. The default values\n+of each threshold are set to heuristically determined values.\ndiff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc\nindex ed7d80c690..bc866c88a8 100644\n--- a/Documentation/git-repo.adoc\n+++ b/Documentation/git-repo.adoc\n@@ -10,7 +10,7 @@ SYNOPSIS\n [synopsis]\n git repo info [--format=(lines|nul) | -z] [--all | <key>...]\n git repo info --keys [--format=(lines|nul) | -z]\n-git repo structure [--format=(table|lines|nul) | -z]\n+git repo structure [--format=(table|lines|nul) | -z] [--[no-]concern]\n \n DESCRIPTION\n -----------\n@@ -56,7 +56,7 @@ supported:\n `nul`:::\n \tSimilar to `lines`, but using a _NUL_ character after each value.\n \n-`structure [--format=(table|lines|nul) | -z]`::\n+`structure [--format=(table|lines|nul) | -z] [--[no-]concern]`::\n \tRetrieve statistics about the current repository structure. The\n \tfollowing kinds of information are reported:\n +\n@@ -88,6 +88,20 @@ supported:\n \tquoted.\n +\n `-z` is an alias for `--format=nul`.\n++\n+To make it easier to know when one of the values is something that\n+users should be concerned about, adding '--concern' will provide a\n+level of concern to be added to the output. In 'table' mode, this will\n+be a table column indicating the level of concern as a number of stars;\n+in `lines` or `nul` mode, a `concern_level` line will be added for each\n+statistic with a numeric value.\n++\n+The level of concern for each statistic is computed using the log_2 of the\n+statistic value. The value is considered concerning if it exceeds a heuristically\n+chosen threshold value; the degree of concern is the difference between the\n+log_2 of the value, and the log_2 of the corresponding threshold.\n++\n+The threshold values are configurable; see the configuration section below.\n \n INFO KEYS\n ---------\n@@ -141,6 +155,15 @@ using the `nul` format:\n git repo info --format=nul layout.bare layout.shallow\n ------------\n \n+CONFIGURATION\n+-------------\n+\n+include::includes/cmd-config-section-all.adoc[]\n+\n+:git-repo: 1\n+include::config/repo.adoc[]\n+\n+\n SEE ALSO\n --------\n linkgit:git-rev-parse[1]\n\n-- \n2.53.0\n\n\n"},{"id":"556516","messageId":"auC7TjiE_KdoTGyO@pks.im","threadId":"66621","inReplyTo":"20261026-b4-repo-structure-concern-v2-1-e0df34228dc8@fastmail.com","subject":"Re: [PATCH v2 1/3] repo: add level of concern reporting","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-10-27T14:55:19Z","receivedAt":"2026-10-27T14:55:19Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Oct 26, 2026 at 01:43:54PM -0400, Mark C. Chu-Carroll wrote:\n> \"git repo structure\" computes a set of statistics\n> that are useful for diagnosing performance issues in\n> a git repository. But for a typical user who hasn't\n> encountered any issues yet, it can be difficult to\n> interpret the data - when should a value be considered\n> concerning?\n> \n> Add an option to \"git repo structure\" that adds a \"degree\n> of concern\" column to the structure report, which indicates\n> when a stat values has reached a point where it might be\n> concerning, and indicate how severe the concern should be.\n\nMissing your Signed-off-by.\n\nI think the commit message should also mention git-sizer(1) here, which\nis the direct inspiration that we have for this additional data. To the\nbest of my knowledge, the level of concern is essentially the last item\nthat is missing for feature compatibility between these two tools.\n\nAnother interesting thing to document here is how you picked the level\nof concern in the first place.\n\n> diff --git a/builtin/repo.c b/builtin/repo.c\n> index 84e012f83f..a450497d4e 100644\n> --- a/builtin/repo.c\n> +++ b/builtin/repo.c\n> @@ -348,8 +348,118 @@ struct stats_table {\n>  \tint name_col_width;\n>  \tint value_col_width;\n>  \tint unit_col_width;\n> +\tint concern_col_width;\n>  };\n>  \n> +/*\n> + * For computing level of concern about a stat, we need to have some\n> + * baseline values. For each of these, we'define a minimum level\n> + * of concern, which will appear as 1 star. Then we scale exponentially:\n> + * each time that value doubles, we add another star.\n> + *\n> + * The threshold values for concern are expressed as log_2 values of the size\n> + * at which the statistic becomes concerning. The default values for the\n> + * threshold were selected based on observed values from a collection of\n> + * representative-ish repositories of varying size, by finding the mean log_2\n> + * value and standard deviation (sigma) for each statistic over the collection.\n> + * The threshold for each metric is then set at mean(log_2) + sigma.\n> + */\n> +struct concern_stat_threshold {\n> +\tconst char *name;\n> +\tsize_t threshold;\n> +};\n> +\n> +enum concern_stat {\n> +\tCONCERN_STAT_COMMIT_COUNT,\n> +\tCONCERN_STAT_TREE_COUNT,\n> +\tCONCERN_STAT_BLOB_COUNT,\n> +\tCONCERN_STAT_TAG_COUNT,\n> +\tCONCERN_STAT_COMMIT_SIZE,\n> +\tCONCERN_STAT_TREE_SIZE,\n> +\tCONCERN_STAT_BLOB_SIZE,\n> +\tCONCERN_STAT_TAG_SIZE,\n> +\tCONCERN_STAT_COMMIT_LARGEST,\n> +\tCONCERN_STAT_TREE_LARGEST,\n> +\tCONCERN_STAT_BLOB_LARGEST,\n> +\tCONCERN_STAT_TAG_LARGEST,\n> +\tCONCERN_STAT_COMMIT_INFLATED_SIZE,\n> +\tCONCERN_STAT_TREE_INFLATED_SIZE,\n> +\tCONCERN_STAT_BLOB_INFLATED_SIZE,\n> +\tCONCERN_STAT_TAG_INFLATED_SIZE,\n> +};\n> +\n> +struct concern_stat_threshold repo_structure_concern_thresholds[] = {\n> +\t{ .name = \"commit_count\", .threshold = 19 },\n> +\t{.name = \"tree_count\", .threshold = 21 },\n\nNit: missing space.\n\n> +\t{ .name = \"blob_count\", .threshold = 22 },\n> +\t{ .name = \"tag_count\", .threshold = 15 },\n> +\t{ .name = \"commit_size\", .threshold = 28 },\n> +\t{ .name = \"tree_size\", .threshold = 27 },\n> +\t{ .name = \"blob_size\", .threshold = 36 },\n> +\t{ .name = \"tags_size\", .threshold = 18 },\n> +\t{ .name = \"commit_largest\", .threshold = 18  },\n> +\t{ .name = \"tree_largest\", .threshold = 18 },\n> +\t{ .name = \"blob_largest\", .threshold = 26 },\n> +\t{ .name = \"tag_largest\", .threshold =  15 },\n> +\t{ .name = \"commit_inflated_size\", .threshold = 28 },\n> +\t{ .name = \"tree_inflated_size\", .threshold = 32 },\n> +\t{ .name = \"blob_inflated_size\", .threshold = 34 },\n> +\t{ .name = \"tag_inflated_size\", .threshold = 25}\n> +};\n\nI think this is a tiny bit unwieldy, as you now have to remember to\nupdate multiple sites whenever you add another metric to this command.\nIdeally, we'd have a single structure that knows to track all\ninformation that we're about to print so that we can define this in a\nsingle, central location.\n\nFor example, we could update `struct object_data` to have a configurable\nthreshold, and then do the same for other such structures. But that\nmight also get unwieldy by itself.\n\nI think what bugs me a bit is that we have not only two, but three\nlocations because we have the enum, the thresholds and then also the\ndata structures themselves.\n\nI'm not quite sure how to fix that myself though.\n\n> +/*\n> + * Concern thresholds aren't universal. We try to set reasonable default\n> + * values, but projects may want to set these thresholds differently. To\n> + * support that, they can set the thresholds for their project in the\n> + * project's git config, in the section \"repo.structure.concern\".\n> +  */\n> +static int config_concern_thresholds(const char *prefixed_var,\n> +\t\t\t\t     const char *str_val,\n> +\t\t\t\t     const struct config_context *ctx,\n> +\t\t\t\t     void *cb UNUSED) {\n\nWe should probably use `cb` to pass around the structure that you\npopulate so that we can avoid global variables.\n\n> +\tconst char *config_prefix = \"repo.structure.concern.\";\n> +\tif (!strncmp(config_prefix, prefixed_var, strlen(config_prefix))) {\n\nWe have `starts_with()`. But also note that if this is a config key then\nit should match case-insensitively, so it should rather be\n`istarts_with()`.\n\nThe config keys are also a bit off as our format only has nested keys up\nto three levels deep, not four.\n\n> +\t\tconst char *concern_name = &prefixed_var[strlen(config_prefix)];\n> +\t\tfor (int i = 0; concern_stat_thresholds[i].name != 0; i++) {\n\nWe typically don't compare with a zero value directly, and indices\nshould be of type `size_t` when iterating, so this should rather be:\n\n        for (size_t i = 0; concern_stat_thresholds[i].name; i++)\n\nBut the loop condition is a bit weird, because you don't have a final\nentry there that has a guaranteed NULL name. So shouldn't we rather\niterate through `ARRAY_SIZE()`?\n\nAlso, I may be blind, but where is that struct getting defined? I can\nonly see `repo_structure_concern_thresholds`.\n\n> +\t\t\tif (!strcmp(concern_stat_thresholds[i].name, concern_name)) {\n> +\t\t\t\tint value = git_config_int(prefixed_var, str_val, ctx->kvi);\n> +\t\t\t\tif (value < 0)\n> +\t\t\t\t\treturn -1;\n> +\t\t\t\tconcern_stat_thresholds[i].threshold = value;\n> +\t\t\t\treturn 0;\n> +\t\t\t}\n> +\t\t}\n> +\t\twarning(_(\"Invalid concern threshold name: '%s'\"), concern_name);\n> +\t\treturn 0;\n> +\t}\n> +\treturn 0;\n> +}\n> +\n> +static size_t approx_log2(long value) {\n\nWe have `log2u()` in \"wrapper.h\". Can we use that instead?\n\n> @@ -681,12 +887,15 @@ static void print_object_data(const char *key, char key_delim,\n>  }\n>  \n>  static void structure_keyvalue_print(struct repo_structure *stats,\n> -\t\t\t\t     char key_delim, char value_delim)\n> +\t\t\t\t     char key_delim, char value_delim,\n> +\t\t\t\t     bool show_concern)\n>  {\n>  \tprint_keyvalue(\"references.branches.count\", key_delim,\n>  \t\t       stats->refs.branches, value_delim);\n> +\n>  \tprint_keyvalue(\"references.tags.count\", key_delim,\n>  \t\t       stats->refs.tags, value_delim);\n> +\n\nNit: stray newlines.\n\n> diff --git a/sideband.c b/sideband.c\n> index 374187402e..0e75ab2317 100644\n> --- a/sideband.c\n> +++ b/sideband.c\n> @@ -1,4 +1,4 @@\n> -#define USE_THE_REPOSITORY_VARIABLE\n> +define USE_THE_REPOSITORY_VARIABLE\n>  #define DISABLE_SIGN_COMPARE_WARNINGS\n>  \n>  #include \"git-compat-util.h\"\n\nThis change probably shouldn't be here.\n\nPatrick\n\n"},{"id":"556517","messageId":"auC7XTg6YXl6xAM3@pks.im","threadId":"66621","inReplyTo":"20261026-b4-repo-structure-concern-v2-2-e0df34228dc8@fastmail.com","subject":"Re: [PATCH v2 2/3] repo: make concern thresholds configurable","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-10-27T14:55:25Z","receivedAt":"2026-10-27T14:55:25Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Oct 26, 2026 at 01:43:55PM -0400, Mark C. Chu-Carroll wrote:\n> diff --git a/builtin/repo.c b/builtin/repo.c\n> index a450497d4e..ed579e6685 100644\n> --- a/builtin/repo.c\n> +++ b/builtin/repo.c\n> @@ -386,40 +389,43 @@ enum concern_stat {\n>  \tCONCERN_STAT_TREE_INFLATED_SIZE,\n>  \tCONCERN_STAT_BLOB_INFLATED_SIZE,\n>  \tCONCERN_STAT_TAG_INFLATED_SIZE,\n> +\tCONCERN_STAT_MAXIMUM\n>  };\n>  \n> -struct concern_stat_threshold repo_structure_concern_thresholds[] = {\n> -\t{ .name = \"commit_count\", .threshold = 19 },\n> -\t{.name = \"tree_count\", .threshold = 21 },\n> -\t{ .name = \"blob_count\", .threshold = 22 },\n> -\t{ .name = \"tag_count\", .threshold = 15 },\n> -\t{ .name = \"commit_size\", .threshold = 28 },\n> -\t{ .name = \"tree_size\", .threshold = 27 },\n> -\t{ .name = \"blob_size\", .threshold = 36 },\n> -\t{ .name = \"tags_size\", .threshold = 18 },\n> -\t{ .name = \"commit_largest\", .threshold = 18  },\n> -\t{ .name = \"tree_largest\", .threshold = 18 },\n> -\t{ .name = \"blob_largest\", .threshold = 26 },\n> -\t{ .name = \"tag_largest\", .threshold =  15 },\n> -\t{ .name = \"commit_inflated_size\", .threshold = 28 },\n> -\t{ .name = \"tree_inflated_size\", .threshold = 32 },\n> -\t{ .name = \"blob_inflated_size\", .threshold = 34 },\n> -\t{ .name = \"tag_inflated_size\", .threshold = 25}\n> +struct concern_stat_threshold concern_stat_thresholds[] = {\n> +\t{ .name = \"commit-count\", .threshold = 19 },\n> +\t{ .name = \"tree-count\", .threshold = 21 },\n> +\t{ .name = \"blob-count\", .threshold = 22 },\n> +\t{ .name = \"tag-count\", .threshold = 15 },\n> +\t{ .name = \"commit-size\", .threshold = 28 },\n> +\t{ .name = \"tree-size\", .threshold = 27 },\n> +\t{ .name = \"blob-size\", .threshold = 36 },\n> +\t{ .name = \"tag-size\", .threshold = 18 },\n> +\t{ .name = \"commit-largest\", .threshold = 18  },\n> +\t{ .name = \"tree-largest\", .threshold = 18 },\n> +\t{ .name = \"blob-largest\", .threshold = 26 },\n> +\t{ .name = \"tag-largest\", .threshold =  15 },\n> +\t{ .name = \"commit-inflated-size\", .threshold = 28 },\n> +\t{ .name = \"tree-inflated-size\", .threshold = 32 },\n> +\t{ .name = \"blob-inflated-size\", .threshold = 34 },\n> +\t{ .name = \"tag-inflated-size\", .threshold = 25},\n> +\t{ 0 }\n>  };\n\nThis commit seems to fix up a couple of issues introduced in the\npreceding commit. We generally require that every patch of a patch\nseries should compile and pass all tests so that we can bisect our\nhistory.\n\nPatrick\n\n"},{"id":"556518","messageId":"auC7YzhTbn3q7GPt@pks.im","threadId":"66621","inReplyTo":"20261026-b4-repo-structure-concern-v2-3-e0df34228dc8@fastmail.com","subject":"Re: [PATCH v2 3/3] repo: add documentation for level-of-concern.","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-10-27T14:55:31Z","receivedAt":"2026-10-27T14:55:31Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Oct 26, 2026 at 01:43:56PM -0400, Mark C. Chu-Carroll wrote:\n> ---\n>  Documentation/config/repo.adoc | 59 ++++++++++++++++++++++++++++++++++++++++++\n>  Documentation/git-repo.adoc    | 27 +++++++++++++++++--\n>  2 files changed, 84 insertions(+), 2 deletions(-)\n> \n> diff --git a/Documentation/config/repo.adoc b/Documentation/config/repo.adoc\n> new file mode 100644\n> index 0000000000..811b099d72\n> --- /dev/null\n> +++ b/Documentation/config/repo.adoc\n> @@ -0,0 +1,59 @@\n> +`repo.concern.commit-count`::\n> +\tSets the value of the concern threshold for commit count.\n> +\n> +`repo.concern.tree-count`::\n> +\tSets the value of the concern threshold for tree count.\n\nI think this commit can be split and squashed into the other two\ncommits:\n\n  - The change to synopsis and documentation of \"--concern\" should be\n    squashed into the first commit.\n\n  - These config values here should be squashed into the second commit.\n\nThanks!\n\nPatrick\n\n"},{"id":"556522","messageId":"20261027-b4-repo-structure-concern-v3-0-98d7a5704170@fastmail.com","threadId":"66621","inReplyTo":"20261021-b4-repo-structure-concern-v1-0-e3010552292c@fastmail.com","subject":"[PATCH v3 0/2] repo: add degree-of-concern reporting to structure","fromName":"Mark C. Chu-Carroll","fromEmail":"markchucarroll@fastmail.com","sentAt":"2026-10-27T15:39:28Z","receivedAt":"2026-10-27T15:39:28Z","isPatch":true,"sender":{"key":"markchucarroll@fastmail.com","avatar":null},"body":"\"git repo structure\" computes a set of statistics that are useful for\ndiagnosing performance issues in a git repository. But for a typical\nuser who hasn't encountered any issues yet, it can be difficult to\ninterpret the data - when should a value be considered concerning?\n\nThis patchset introduces a feature to \"git repo structure\" that reports\non how concerning certain measures of storage structure  are for a\nrepository.  In table mode, concern is displayed as a column containing\na number of stars indicating the degree of concern; in line or nul mode,\nconcerns are displayed as additionaL lines with a numeric value.\n\nTo select defaults for the thresholds where a value becomes concern,\na (hopefully) representative selection of repositories were analyzed.\nBased on empirical exploration of the data, the measurement that seemed\nclosest to accurately assessing when a repository's structure becomes\nconcerning was based on the base 2 logarithm of the value. Across all of\nthe sample repositories, for all of the measurements, the point at which\nthings become problematic was when a base 2 logarithm of a value was\nmore than 1 standard deviation from the mean of the log_2 values in\nthe sample set.  Based on this, the concern threshold was set for each\nof the measures.\n\nWhile this seems like a reasonable approach for setting default\nthresholds, it's obviously still very ad-hoc. It's likely that over\ntime, the default values will evolve; and in the short term, projects\nmay want to set their own thresholds. So this patchset includes support\nfor configuring the thresholds on a per-reposiry basis.\n\nOverview of the change:\n\n* The first commit adds the command-line option \"--concern\", and creates the basic implementation of concern reporting.\n* The next commit adds configurability for the concern thresholds.\n\nSigned-off-by: Mark C. Chu-Carroll <markchucarroll@fastmail.com>\n---\nChanges in v3:\n- Merge the documentation commit backwards into the other two commits.\n- Link to v2: https://patch.msgid.link/20261026-b4-repo-structure-concern-v2-0-e0df34228dc8@fastmail.com\n\nChanges in v2:\n- Fix memory leak in concern string generation.\n- Fix CI/CD test issue involving zlib versions.\n- Updating the naming of the config value prefix.\n- Link to v1: https://patch.msgid.link/20261021-b4-repo-structure-concern-v1-0-e3010552292c@fastmail.com\n\n---\nMark C. Chu-Carroll (2):\n      repo: add level of concern reporting\n      repo: make concern thresholds configurable\n\n Documentation/config/repo.adoc |  59 +++++++\n Documentation/git-repo.adoc    |  27 +++-\n builtin/repo.c                 | 343 ++++++++++++++++++++++++++++++++++++++---\n t/t1901-repo-structure.sh      | 137 ++++++++++++++++\n 4 files changed, 543 insertions(+), 23 deletions(-)\n\nRange-diff versus v2:\n\n1:  b1ef56dd47 ! 1:  11c60f6d6d repo: add level of concern reporting\n    @@ Commit message\n         when a stat values has reached a point where it might be\n         concerning, and indicate how severe the concern should be.\n     \n    + ## Documentation/git-repo.adoc ##\n    +@@ Documentation/git-repo.adoc: SYNOPSIS\n    + [synopsis]\n    + git repo info [--format=(lines|nul) | -z] [--all | <key>...]\n    + git repo info --keys [--format=(lines|nul) | -z]\n    +-git repo structure [--format=(table|lines|nul) | -z]\n    ++git repo structure [--format=(table|lines|nul) | -z] [--[no-]concern]\n    + \n    + DESCRIPTION\n    + -----------\n    +@@ Documentation/git-repo.adoc: supported:\n    + `nul`:::\n    + \tSimilar to `lines`, but using a _NUL_ character after each value.\n    + \n    +-`structure [--format=(table|lines|nul) | -z]`::\n    ++`structure [--format=(table|lines|nul) | -z] [--[no-]concern]`::\n    + \tRetrieve statistics about the current repository structure. The\n    + \tfollowing kinds of information are reported:\n    + +\n    +@@ Documentation/git-repo.adoc: supported:\n    + \tquoted.\n    + +\n    + `-z` is an alias for `--format=nul`.\n    +++\n    ++To make it easier to know when one of the values is something that\n    ++users should be concerned about, adding '--concern' will provide a\n    ++level of concern to be added to the output. In 'table' mode, this will\n    ++be a table column indicating the level of concern as a number of stars;\n    ++in `lines` or `nul` mode, a `concern_level` line will be added for each\n    ++statistic with a numeric value.\n    +++\n    ++The level of concern for each statistic is computed using the log_2 of the\n    ++statistic value. The value is considered concerning if it exceeds a heuristically\n    ++chosen threshold value; the degree of concern is the difference between the\n    ++log_2 of the value, and the log_2 of the corresponding threshold.\n    + \n    + INFO KEYS\n    + ---------\n    +\n      ## builtin/repo.c ##\n     @@\n      \t\"git repo info --keys [--format=(lines|nul) | -z]\"\n2:  446391b362 ! 2:  4eba5072b0 repo: make concern thresholds configurable\n    @@ Commit message\n         configure per-repo concern levels for \"git repo structure\"\n         concern reporting.\n     \n    + ## Documentation/config/repo.adoc (new) ##\n    +@@\n    ++`repo.concern.commit-count`::\n    ++\tSets the value of the concern threshold for commit count.\n    ++\n    ++`repo.concern.tree-count`::\n    ++\tSets the value of the concern threshold for tree count.\n    ++\n    ++`repo.concern.blob-count`::\n    ++\tSets the value of the concern threshold for blob count.\n    ++\n    ++`repo.concern.tag-count`::\n    ++\tSets the value of the concern threshold for tag count.\n    ++\n    ++`repo.concern.commit-size`::\n    ++\tSets the value of the concern threshold for total commit size.\n    ++\n    ++`repo.concern.tree-size`::\n    ++\tSets the value of the concern threshold for total tree size.\n    ++\n    ++`repo.concern.blob-size`::\n    ++\tSets the value of the concern threshold for total blob size.\n    ++\n    ++`repo.concern.tag-size`::\n    ++\tSets the value of the concern threshold for total tag size.\n    ++\n    ++`repo.concern.commit-inflated-size`::\n    ++\tSets the value of the concern threshold for total size\n    ++\tof all inflated commits.\n    ++\n    ++`repo.concern.tree-inflated-size`::\n    ++\tSets the value of the concern threshold for total size of\n    ++\tall inflated trees.\n    ++\n    ++`repo.concern.blob-inflated-size`::\n    ++\tSets the value of the concern threshold for total size of\n    ++\tall inflated blobs.\n    ++\n    ++`repo.concern.tag-inflated-size`::\n    ++\tSets the value of the concern threshold for the total size\n    ++\tof all inflated tags.\n    ++\n    ++`repo.concern.commit-largest`::\n    ++\tSets the value of the concern threshold the size of the largest\n    ++\tcommit.\n    ++\n    ++`repo.concern.tree-largest`::\n    ++\tSets the value of the concern threshold for the size of the\n    ++\tlargest tree.\n    ++\n    ++`repo.concern.blob-largest`::\n    ++\tSets the value of the concern threshold for the size of the\n    ++\tlargest blob.\n    ++\n    ++`repo.concern.tag-largest`::\n    ++\tSets the value of the concern threshold for the size of the\n    ++\tlargest tag.\n    ++\n    ++All of the concern thresholds are integer values for the\n    ++floor of the base-2 log of the threshold value. The default values\n    ++of each threshold are set to heuristically determined values.\n    +\n    + ## Documentation/git-repo.adoc ##\n    +@@ Documentation/git-repo.adoc: statistic value. The value is considered concerning if it exceeds a heuristicall\n    + chosen threshold value; the degree of concern is the difference between the\n    + log_2 of the value, and the log_2 of the corresponding threshold.\n    + \n    ++The threshold values are configurable; see the configuration section below.\n    ++\n    + INFO KEYS\n    + ---------\n    + In order to obtain a set of values from `git repo info`, you should provide\n    +@@ Documentation/git-repo.adoc: using the `nul` format:\n    + git repo info --format=nul layout.bare layout.shallow\n    + ------------\n    + \n    ++CONFIGURATION\n    ++-------------\n    ++\n    ++include::includes/cmd-config-section-all.adoc[]\n    ++\n    ++:git-repo: 1\n    ++include::config/repo.adoc[]\n    ++\n    ++\n    + SEE ALSO\n    + --------\n    + linkgit:git-rev-parse[1]\n    +\n      ## builtin/repo.c ##\n     @@\n      \n3:  92f1fafbcd < -:  ---------- repo: add documentation for level-of-concern.\n\n---\nbase-commit: 3bc0341126508f78f5869cbfc0005e987efdf0c7\nchange-id: 20261019-b4-repo-structure-concern-f1d6f6326095\n\n\n"},{"id":"556523","messageId":"20261027-b4-repo-structure-concern-v3-1-98d7a5704170@fastmail.com","threadId":"66621","inReplyTo":"20261027-b4-repo-structure-concern-v3-0-98d7a5704170@fastmail.com","subject":"[PATCH v3 1/2] repo: add level of concern reporting","fromName":"Mark C. Chu-Carroll","fromEmail":"markchucarroll@fastmail.com","sentAt":"2026-10-27T15:39:29Z","receivedAt":"2026-10-27T15:39:29Z","isPatch":true,"sender":{"key":"markchucarroll@fastmail.com","avatar":null},"body":"\"git repo structure\" computes a set of statistics\nthat are useful for diagnosing performance issues in\na git repository. But for a typical user who hasn't\nencountered any issues yet, it can be difficult to\ninterpret the data - when should a value be considered\nconcerning?\n\nAdd an option to \"git repo structure\" that adds a \"degree\nof concern\" column to the structure report, which indicates\nwhen a stat values has reached a point where it might be\nconcerning, and indicate how severe the concern should be.\n---\n Documentation/git-repo.adoc |  16 ++-\n builtin/repo.c              | 329 +++++++++++++++++++++++++++++++++++++++++---\n sideband.c                  |   2 +-\n t/t1901-repo-structure.sh   | 125 ++++++++---------\n 4 files changed, 380 insertions(+), 92 deletions(-)\n\ndiff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc\nindex ed7d80c690..f186b27d87 100644\n--- a/Documentation/git-repo.adoc\n+++ b/Documentation/git-repo.adoc\n@@ -10,7 +10,7 @@ SYNOPSIS\n [synopsis]\n git repo info [--format=(lines|nul) | -z] [--all | <key>...]\n git repo info --keys [--format=(lines|nul) | -z]\n-git repo structure [--format=(table|lines|nul) | -z]\n+git repo structure [--format=(table|lines|nul) | -z] [--[no-]concern]\n \n DESCRIPTION\n -----------\n@@ -56,7 +56,7 @@ supported:\n `nul`:::\n \tSimilar to `lines`, but using a _NUL_ character after each value.\n \n-`structure [--format=(table|lines|nul) | -z]`::\n+`structure [--format=(table|lines|nul) | -z] [--[no-]concern]`::\n \tRetrieve statistics about the current repository structure. The\n \tfollowing kinds of information are reported:\n +\n@@ -88,6 +88,18 @@ supported:\n \tquoted.\n +\n `-z` is an alias for `--format=nul`.\n++\n+To make it easier to know when one of the values is something that\n+users should be concerned about, adding '--concern' will provide a\n+level of concern to be added to the output. In 'table' mode, this will\n+be a table column indicating the level of concern as a number of stars;\n+in `lines` or `nul` mode, a `concern_level` line will be added for each\n+statistic with a numeric value.\n++\n+The level of concern for each statistic is computed using the log_2 of the\n+statistic value. The value is considered concerning if it exceeds a heuristically\n+chosen threshold value; the degree of concern is the difference between the\n+log_2 of the value, and the log_2 of the corresponding threshold.\n \n INFO KEYS\n ---------\ndiff --git a/builtin/repo.c b/builtin/repo.c\nindex 84e012f83f..a450497d4e 100644\n--- a/builtin/repo.c\n+++ b/builtin/repo.c\n@@ -27,7 +27,7 @@\n \t\"git repo info --keys [--format=(lines|nul) | -z]\"\n \n #define REPO_STRUCTURE_USAGE \\\n-\t\"git repo structure [--format=(table|lines|nul) | -z]\"\n+\t\"git repo structure [--format=(table|lines|nul) | -z] [--[no-]concern]\"\n \n static const char *const repo_usage[] = {\n \tREPO_INFO_USAGE,\n@@ -348,8 +348,118 @@ struct stats_table {\n \tint name_col_width;\n \tint value_col_width;\n \tint unit_col_width;\n+\tint concern_col_width;\n };\n \n+/*\n+ * For computing level of concern about a stat, we need to have some\n+ * baseline values. For each of these, we'define a minimum level\n+ * of concern, which will appear as 1 star. Then we scale exponentially:\n+ * each time that value doubles, we add another star.\n+ *\n+ * The threshold values for concern are expressed as log_2 values of the size\n+ * at which the statistic becomes concerning. The default values for the\n+ * threshold were selected based on observed values from a collection of\n+ * representative-ish repositories of varying size, by finding the mean log_2\n+ * value and standard deviation (sigma) for each statistic over the collection.\n+ * The threshold for each metric is then set at mean(log_2) + sigma.\n+ */\n+struct concern_stat_threshold {\n+\tconst char *name;\n+\tsize_t threshold;\n+};\n+\n+enum concern_stat {\n+\tCONCERN_STAT_COMMIT_COUNT,\n+\tCONCERN_STAT_TREE_COUNT,\n+\tCONCERN_STAT_BLOB_COUNT,\n+\tCONCERN_STAT_TAG_COUNT,\n+\tCONCERN_STAT_COMMIT_SIZE,\n+\tCONCERN_STAT_TREE_SIZE,\n+\tCONCERN_STAT_BLOB_SIZE,\n+\tCONCERN_STAT_TAG_SIZE,\n+\tCONCERN_STAT_COMMIT_LARGEST,\n+\tCONCERN_STAT_TREE_LARGEST,\n+\tCONCERN_STAT_BLOB_LARGEST,\n+\tCONCERN_STAT_TAG_LARGEST,\n+\tCONCERN_STAT_COMMIT_INFLATED_SIZE,\n+\tCONCERN_STAT_TREE_INFLATED_SIZE,\n+\tCONCERN_STAT_BLOB_INFLATED_SIZE,\n+\tCONCERN_STAT_TAG_INFLATED_SIZE,\n+};\n+\n+struct concern_stat_threshold repo_structure_concern_thresholds[] = {\n+\t{ .name = \"commit_count\", .threshold = 19 },\n+\t{.name = \"tree_count\", .threshold = 21 },\n+\t{ .name = \"blob_count\", .threshold = 22 },\n+\t{ .name = \"tag_count\", .threshold = 15 },\n+\t{ .name = \"commit_size\", .threshold = 28 },\n+\t{ .name = \"tree_size\", .threshold = 27 },\n+\t{ .name = \"blob_size\", .threshold = 36 },\n+\t{ .name = \"tags_size\", .threshold = 18 },\n+\t{ .name = \"commit_largest\", .threshold = 18  },\n+\t{ .name = \"tree_largest\", .threshold = 18 },\n+\t{ .name = \"blob_largest\", .threshold = 26 },\n+\t{ .name = \"tag_largest\", .threshold =  15 },\n+\t{ .name = \"commit_inflated_size\", .threshold = 28 },\n+\t{ .name = \"tree_inflated_size\", .threshold = 32 },\n+\t{ .name = \"blob_inflated_size\", .threshold = 34 },\n+\t{ .name = \"tag_inflated_size\", .threshold = 25}\n+};\n+\n+/*\n+ * Concern thresholds aren't universal. We try to set reasonable default\n+ * values, but projects may want to set these thresholds differently. To\n+ * support that, they can set the thresholds for their project in the\n+ * project's git config, in the section \"repo.structure.concern\".\n+  */\n+static int config_concern_thresholds(const char *prefixed_var,\n+\t\t\t\t     const char *str_val,\n+\t\t\t\t     const struct config_context *ctx,\n+\t\t\t\t     void *cb UNUSED) {\n+\tconst char *config_prefix = \"repo.structure.concern.\";\n+\tif (!strncmp(config_prefix, prefixed_var, strlen(config_prefix))) {\n+\t\tconst char *concern_name = &prefixed_var[strlen(config_prefix)];\n+\t\tfor (int i = 0; concern_stat_thresholds[i].name != 0; i++) {\n+\t\t\tif (!strcmp(concern_stat_thresholds[i].name, concern_name)) {\n+\t\t\t\tint value = git_config_int(prefixed_var, str_val, ctx->kvi);\n+\t\t\t\tif (value < 0)\n+\t\t\t\t\treturn -1;\n+\t\t\t\tconcern_stat_thresholds[i].threshold = value;\n+\t\t\t\treturn 0;\n+\t\t\t}\n+\t\t}\n+\t\twarning(_(\"Invalid concern threshold name: '%s'\"), concern_name);\n+\t\treturn 0;\n+\t}\n+\treturn 0;\n+}\n+\n+static size_t approx_log2(long value) {\n+\tint position = 0;\n+\tif (value <= 0) {\n+\t\treturn 0;\n+\t}\n+\twhile (value > 1) {\n+\t\tposition++;\n+\t\tvalue >>= 1;\n+\t}\n+\treturn position;\n+}\n+\n+static size_t repo_structure_concern_level(enum concern_stat stat_id,\n+\t\t\t\t\t   size_t value) {\n+\tsize_t log_value;\n+\tif (stat_id >= ARRAY_SIZE(concern_stat_thresholds))\n+\t\tBUG(\"unexpected repository stat type\");\n+\tlog_value = approx_log2(value);\n+\tif (log_value > concern_stat_thresholds[stat_id].threshold)\n+\t\treturn log_value -\n+\t\t\tconcern_stat_thresholds[stat_id].threshold;\n+\telse\n+\t\treturn 0;\n+}\n+\n /*\n  * Holds column data that gets stored for each row.\n  */\n@@ -358,6 +468,7 @@ struct stats_table_entry {\n \tconst char *unit;\n \tsize_t index;\n \tstruct object_id *oid;\n+\tsize_t concern;\n };\n \n static void stats_table_vaddf(struct stats_table *table,\n@@ -391,6 +502,8 @@ static void stats_table_vaddf(struct stats_table *table,\n \t\tif (value_width > table->value_col_width)\n \t\t\ttable->value_col_width = value_width;\n \t}\n+\tif ((int)entry->concern > table->concern_col_width)\n+\t\ttable->concern_col_width = entry->concern;\n \tif (entry->unit) {\n \t\tint unit_width = utf8_strwidth(entry->unit);\n \t\tif (unit_width > table->unit_col_width)\n@@ -410,13 +523,14 @@ static void stats_table_addf(struct stats_table *table, const char *format, ...)\n }\n \n static void stats_table_count_addf(struct stats_table *table, size_t value,\n-\t\t\t\t   const char *format, ...)\n+\t\t\t\t   size_t concern, const char *format, ...)\n {\n \tstruct stats_table_entry *entry;\n \tva_list ap;\n \n \tCALLOC_ARRAY(entry, 1);\n \thumanise_count(value, &entry->value, &entry->unit);\n+\tentry->concern = concern;\n \n \tva_start(ap, format);\n \tstats_table_vaddf(table, entry, format, ap);\n@@ -425,13 +539,14 @@ static void stats_table_count_addf(struct stats_table *table, size_t value,\n \n static void stats_table_object_count_addf(struct stats_table *table,\n \t\t\t\t\t  struct object_id *oid, size_t value,\n-\t\t\t\t\t  const char *format, ...)\n+\t\t\t\t\t  size_t concern, const char *format, ...)\n {\n \tstruct stats_table_entry *entry;\n \tva_list ap;\n \n \tCALLOC_ARRAY(entry, 1);\n \thumanise_count(value, &entry->value, &entry->unit);\n+\tentry->concern = concern;\n \n \t/*\n \t * A NULL OID should not have a table annotation.\n@@ -445,6 +560,7 @@ static void stats_table_object_count_addf(struct stats_table *table,\n }\n \n static void stats_table_size_addf(struct stats_table *table, size_t value,\n+\t\t\t\t  size_t concern,\n \t\t\t\t  const char *format, ...)\n {\n \tstruct stats_table_entry *entry;\n@@ -452,6 +568,7 @@ static void stats_table_size_addf(struct stats_table *table, size_t value,\n \n \tCALLOC_ARRAY(entry, 1);\n \thumanise_bytes(value, &entry->value, &entry->unit, HUMANISE_COMPACT);\n+\tentry->concern = concern;\n \n \tva_start(ap, format);\n \tstats_table_vaddf(table, entry, format, ap);\n@@ -460,6 +577,7 @@ static void stats_table_size_addf(struct stats_table *table, size_t value,\n \n static void stats_table_object_size_addf(struct stats_table *table,\n \t\t\t\t\t struct object_id *oid, size_t value,\n+\t\t\t\t\t size_t concern,\n \t\t\t\t\t const char *format, ...)\n {\n \tstruct stats_table_entry *entry;\n@@ -467,6 +585,7 @@ static void stats_table_object_size_addf(struct stats_table *table,\n \n \tCALLOC_ARRAY(entry, 1);\n \thumanise_bytes(value, &entry->value, &entry->unit, HUMANISE_COMPACT);\n+\tentry->concern = concern;\n \n \t/*\n \t * A NULL OID should not have a table annotation.\n@@ -501,47 +620,92 @@ static void stats_table_setup_structure(struct stats_table *table,\n \n \tref_total = get_total_reference_count(refs);\n \tstats_table_addf(table, \"* %s\", _(\"References\"));\n-\tstats_table_count_addf(table, ref_total, \"  * %s\", _(\"Count\"));\n-\tstats_table_count_addf(table, refs->branches, \"    * %s\", _(\"Branches\"));\n-\tstats_table_count_addf(table, refs->tags, \"    * %s\", _(\"Tags\"));\n-\tstats_table_count_addf(table, refs->remotes, \"    * %s\", _(\"Remotes\"));\n-\tstats_table_count_addf(table, refs->others, \"    * %s\", _(\"Others\"));\n+\tstats_table_count_addf(table, ref_total, 0,\n+\t\t\t       \"  * %s\", _(\"Count\"));\n+\tstats_table_count_addf(table, refs->branches, 0,\n+\t\t\t       \"    * %s\", _(\"Branches\"));\n+\tstats_table_count_addf(table, refs->tags,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_TAG_COUNT,\n+\t\t\t\t       refs->tags),\n+\t\t\t       \"    * %s\", _(\"Tags\"));\n+\tstats_table_count_addf(table, refs->remotes, 0,\n+\t\t\t       \"    * %s\", _(\"Remotes\"));\n+\tstats_table_count_addf(table, refs->others, 0,\n+\t\t\t       \"    * %s\", _(\"Others\"));\n \n \tobject_count_total = get_total_object_values(&objects->type_counts);\n \tstats_table_addf(table, \"\");\n \tstats_table_addf(table, \"* %s\", _(\"Reachable objects\"));\n-\tstats_table_count_addf(table, object_count_total, \"  * %s\", _(\"Count\"));\n+\tstats_table_count_addf(table, object_count_total, 0,\n+\t\t\t       \"  * %s\", _(\"Count\"));\n \tstats_table_count_addf(table, objects->type_counts.commits,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_COMMIT_COUNT,\n+\t\t\t\t       objects->type_counts.commits),\n \t\t\t       \"    * %s\", _(\"Commits\"));\n \tstats_table_count_addf(table, objects->type_counts.trees,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_TREE_COUNT,\n+\t\t\t\t       objects->type_counts.trees),\n \t\t\t       \"    * %s\", _(\"Trees\"));\n \tstats_table_count_addf(table, objects->type_counts.blobs,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_BLOB_COUNT,\n+\t\t\t\t       objects->type_counts.blobs),\n \t\t\t       \"    * %s\", _(\"Blobs\"));\n \tstats_table_count_addf(table, objects->type_counts.tags,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_TAG_COUNT,\n+\t\t\t\t       objects->type_counts.tags),\n \t\t\t       \"    * %s\", _(\"Tags\"));\n \n \tinflated_object_total = get_total_object_values(&objects->inflated_sizes);\n-\tstats_table_size_addf(table, inflated_object_total,\n+\tstats_table_size_addf(table, inflated_object_total, 0,\n \t\t\t      \"  * %s\", _(\"Inflated size\"));\n \tstats_table_size_addf(table, objects->inflated_sizes.commits,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_COMMIT_INFLATED_SIZE,\n+\t\t\t\t      objects->inflated_sizes.commits),\n \t\t\t      \"    * %s\", _(\"Commits\"));\n \tstats_table_size_addf(table, objects->inflated_sizes.trees,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_TREE_INFLATED_SIZE,\n+\t\t\t\t      objects->inflated_sizes.trees),\n \t\t\t      \"    * %s\", _(\"Trees\"));\n \tstats_table_size_addf(table, objects->inflated_sizes.blobs,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_BLOB_INFLATED_SIZE,\n+\t\t\t\t      objects->inflated_sizes.blobs),\n \t\t\t      \"    * %s\", _(\"Blobs\"));\n \tstats_table_size_addf(table, objects->inflated_sizes.tags,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_TAG_INFLATED_SIZE,\n+\t\t\t\t      objects->inflated_sizes.tags),\n \t\t\t      \"    * %s\", _(\"Tags\"));\n \n \tdisk_object_total = get_total_object_values(&objects->disk_sizes);\n-\tstats_table_size_addf(table, disk_object_total,\n+\tstats_table_size_addf(table, disk_object_total, 0,\n \t\t\t      \"  * %s\", _(\"Disk size\"));\n \tstats_table_size_addf(table, objects->disk_sizes.commits,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_COMMIT_SIZE,\n+\t\t\t\t      objects->disk_sizes.commits),\n \t\t\t      \"    * %s\", _(\"Commits\"));\n \tstats_table_size_addf(table, objects->disk_sizes.trees,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_TREE_SIZE,\n+\t\t\t\t      objects->disk_sizes.trees),\n \t\t\t      \"    * %s\", _(\"Trees\"));\n \tstats_table_size_addf(table, objects->disk_sizes.blobs,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_BLOB_SIZE,\n+\t\t\t\t      objects->disk_sizes.blobs),\n \t\t\t      \"    * %s\", _(\"Blobs\"));\n \tstats_table_size_addf(table, objects->disk_sizes.tags,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_TAG_SIZE,\n+\t\t\t\t      objects->disk_sizes.tags),\n \t\t\t      \"    * %s\", _(\"Tags\"));\n \n \tstats_table_addf(table, \"\");\n@@ -550,51 +714,71 @@ static void stats_table_setup_structure(struct stats_table *table,\n \tstats_table_object_size_addf(table,\n \t\t\t\t     &objects->largest.commit_size.oid,\n \t\t\t\t     objects->largest.commit_size.value,\n+\t\t\t\t     repo_structure_concern_level(\n+\t\t\t\t\t     CONCERN_STAT_COMMIT_LARGEST,\n+\t\t\t\t\t     objects->largest.commit_size.value),\n \t\t\t\t     \"    * %s\", _(\"Maximum size\"));\n \tstats_table_object_count_addf(table,\n \t\t\t\t      &objects->largest.parent_count.oid,\n \t\t\t\t      objects->largest.parent_count.value,\n+\t\t\t\t      0,\n \t\t\t\t      \"    * %s\", _(\"Maximum parents\"));\n \tstats_table_addf(table, \"  * %s\", _(\"Trees\"));\n \tstats_table_object_size_addf(table,\n \t\t\t\t     &objects->largest.tree_size.oid,\n \t\t\t\t     objects->largest.tree_size.value,\n+\t\t\t\t      repo_structure_concern_level(\n+\t\t\t\t\t      CONCERN_STAT_TREE_LARGEST,\n+\t\t\t\t\t      objects->largest.tree_size.value),\n \t\t\t\t     \"    * %s\", _(\"Maximum size\"));\n \tstats_table_object_count_addf(table,\n \t\t\t\t      &objects->largest.tree_entries.oid,\n \t\t\t\t      objects->largest.tree_entries.value,\n+\t\t\t\t      0,\n \t\t\t\t      \"    * %s\", _(\"Maximum entries\"));\n \tstats_table_addf(table, \"  * %s\", _(\"Blobs\"));\n \tstats_table_object_size_addf(table,\n \t\t\t\t     &objects->largest.blob_size.oid,\n \t\t\t\t     objects->largest.blob_size.value,\n+\t\t\t\t     repo_structure_concern_level(\n+\t\t\t\t\t     CONCERN_STAT_BLOB_LARGEST,\n+\t\t\t\t\t     objects->largest.blob_size.value),\n \t\t\t\t     \"    * %s\", _(\"Maximum size\"));\n \tstats_table_addf(table, \"  * %s\", _(\"Tags\"));\n \tstats_table_object_size_addf(table,\n \t\t\t\t     &objects->largest.tag_size.oid,\n \t\t\t\t     objects->largest.tag_size.value,\n+\t\t\t\t     repo_structure_concern_level(\n+\t\t\t\t\t     CONCERN_STAT_TAG_LARGEST,\n+\t\t\t\t\t     objects->largest.tag_size.value),\n \t\t\t\t     \"    * %s\", _(\"Maximum size\"));\n }\n \n #define INDEX_WIDTH 4\n \n-static void stats_table_print_structure(const struct stats_table *table)\n+static void stats_table_print_structure(const struct stats_table *table,\n+\t\t\t\t\tbool show_concern)\n {\n \tconst char *name_col_title = _(\"Repository structure\");\n \tconst char *value_col_title = _(\"Value\");\n+\tconst char *concern_col_title = _(\"Concern\");\n \tint title_name_width = utf8_strwidth(name_col_title);\n \tint title_value_width = utf8_strwidth(value_col_title);\n+\tint title_concern_width = utf8_strwidth(concern_col_title);\n \tint name_col_width = table->name_col_width;\n \tint value_col_width = table->value_col_width;\n \tint unit_col_width = table->unit_col_width;\n+\tint concern_col_width = table->concern_col_width;\n \tstruct string_list_item *item;\n \tstruct strbuf buf = STRBUF_INIT;\n+\tstruct strbuf concern_str = STRBUF_INIT;\n \n \tif (title_name_width > name_col_width)\n \t\tname_col_width = title_name_width;\n \tif (title_value_width > value_col_width + unit_col_width + 1)\n \t\tvalue_col_width = title_value_width - unit_col_width;\n-\n+\tif (show_concern && title_concern_width > concern_col_width)\n+\t\tconcern_col_width = title_concern_width;\n \tstrbuf_addstr(&buf, \"| \");\n \tstrbuf_utf8_align(&buf, ALIGN_LEFT, name_col_width + INDEX_WIDTH,\n \t\t\t  name_col_title);\n@@ -602,6 +786,12 @@ static void stats_table_print_structure(const struct stats_table *table)\n \tstrbuf_utf8_align(&buf, ALIGN_LEFT,\n \t\t\t  value_col_width + unit_col_width + 1, value_col_title);\n \tstrbuf_addstr(&buf, \" |\");\n+\tif (show_concern) {\n+\t\tstrbuf_addch(&buf, ' ');\n+\t\tstrbuf_utf8_align(&buf, ALIGN_LEFT, concern_col_width,\n+\t\t\t  concern_col_title);\n+\t\tstrbuf_addstr(&buf, \" |\");\n+\t}\n \tprintf(\"%s\\n\", buf.buf);\n \n \tprintf(\"| \");\n@@ -610,17 +800,28 @@ static void stats_table_print_structure(const struct stats_table *table)\n \tprintf(\" | \");\n \tfor (int i = 0; i < value_col_width + unit_col_width + 1; i++)\n \t\tputchar('-');\n-\tprintf(\" |\\n\");\n+\tprintf(\" |\");\n+\tif (show_concern) {\n+\t\tputchar(' ');\n+\t\tfor (int i = 0; i < concern_col_width; i++) {\n+\t\t\tputchar('-');\n+\t\t}\n+\t\tprintf(\" |\");\n+\t}\n+\tputchar('\\n');\n \n \tfor_each_string_list_item(item, &table->rows) {\n \t\tstruct stats_table_entry *entry = item->util;\n \t\tconst char *value = \"\";\n \t\tconst char *unit = \"\";\n+\t\tstrbuf_reset(&concern_str);\n \n \t\tif (entry) {\n \t\t\tvalue = entry->value;\n \t\t\tif (entry->unit)\n \t\t\t\tunit = entry->unit;\n+\t\t\tif (entry->concern > 0)\n+\t\t\t\tstrbuf_addchars(&concern_str, '*', entry->concern);\n \t\t}\n \n \t\tstrbuf_reset(&buf);\n@@ -638,6 +839,11 @@ static void stats_table_print_structure(const struct stats_table *table)\n \t\tstrbuf_addch(&buf, ' ');\n \t\tstrbuf_utf8_align(&buf, ALIGN_LEFT, unit_col_width, unit);\n \t\tstrbuf_addstr(&buf, \" |\");\n+\t\tif (show_concern) {\n+\t\t\tstrbuf_addch(&buf, ' ');\n+\t\t\tstrbuf_utf8_align(&buf, ALIGN_LEFT, concern_col_width, concern_str.buf);\n+\t\t\tstrbuf_addstr(&buf, \" |\");\n+\t\t}\n \t\tprintf(\"%s\\n\", buf.buf);\n \t}\n \n@@ -681,12 +887,15 @@ static void print_object_data(const char *key, char key_delim,\n }\n \n static void structure_keyvalue_print(struct repo_structure *stats,\n-\t\t\t\t     char key_delim, char value_delim)\n+\t\t\t\t     char key_delim, char value_delim,\n+\t\t\t\t     bool show_concern)\n {\n \tprint_keyvalue(\"references.branches.count\", key_delim,\n \t\t       stats->refs.branches, value_delim);\n+\n \tprint_keyvalue(\"references.tags.count\", key_delim,\n \t\t       stats->refs.tags, value_delim);\n+\n \tprint_keyvalue(\"references.remotes.count\", key_delim,\n \t\t       stats->refs.remotes, value_delim);\n \tprint_keyvalue(\"references.others.count\", key_delim,\n@@ -694,39 +903,120 @@ static void structure_keyvalue_print(struct repo_structure *stats,\n \n \tprint_keyvalue(\"objects.commits.count\", key_delim,\n \t\t       stats->objects.type_counts.commits, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.commits.count.concern_level\", key_delim,\n+\t\t\t      repo_structure_concern_level(CONCERN_STAT_COMMIT_COUNT,\n+\t\t\t\t\t\t\t   stats->objects.type_counts.commits),\n+\t\t\t      value_delim);\n \tprint_keyvalue(\"objects.trees.count\", key_delim,\n \t\t       stats->objects.type_counts.trees, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.trees.count.concern_level\", key_delim,\n+\t\t\t      repo_structure_concern_level(CONCERN_STAT_TREE_COUNT,\n+\t\t\t\t\t\t\t   stats->objects.type_counts.trees),\n+\t\t\t      value_delim);\n \tprint_keyvalue(\"objects.blobs.count\", key_delim,\n \t\t       stats->objects.type_counts.blobs, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.blobs.count.concern_level\", key_delim,\n+\t\t\t      repo_structure_concern_level(CONCERN_STAT_BLOB_COUNT,\n+\t\t\t\t\t\t\t   stats->objects.type_counts.blobs),\n+\t\t\t      value_delim);\n \tprint_keyvalue(\"objects.tags.count\", key_delim,\n \t\t       stats->objects.type_counts.tags, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.tags.count.concern_level\", key_delim,\n+\t\t\t      repo_structure_concern_level(CONCERN_STAT_TAG_COUNT,\n+\t\t\t\t\t\t\t   stats->objects.type_counts.tags),\n+\t\t\t      value_delim);\n \n \tprint_keyvalue(\"objects.commits.inflated_size\", key_delim,\n \t\t       stats->objects.inflated_sizes.commits, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.commits.inflated_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_COMMIT_INFLATED_SIZE,\n+\t\t\t\t\t\t\t    stats->objects.inflated_sizes.commits),\n+\t\t\t      value_delim);\n \tprint_keyvalue(\"objects.trees.inflated_size\", key_delim,\n \t\t       stats->objects.inflated_sizes.trees, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.trees.inflated_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_TREE_INFLATED_SIZE,\n+\t\t\t\t\t\t\t    stats->objects.inflated_sizes.trees),\n+\t\t\t      value_delim);\n+\n \tprint_keyvalue(\"objects.blobs.inflated_size\", key_delim,\n \t\t       stats->objects.inflated_sizes.blobs, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.blobs.inflated_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_BLOB_INFLATED_SIZE,\n+\t\t\t\t\t\t\t    stats->objects.inflated_sizes.blobs),\n+\t\t\t      value_delim);\n \tprint_keyvalue(\"objects.tags.inflated_size\", key_delim,\n \t\t       stats->objects.inflated_sizes.tags, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.tags.inflated_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_TAG_INFLATED_SIZE,\n+\t\t\t\t\t\t\t    stats->objects.inflated_sizes.tags),\n+\t\t\t      value_delim);\n \n \tprint_keyvalue(\"objects.commits.disk_size\", key_delim,\n \t\t       stats->objects.disk_sizes.commits, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.commits.disk_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_COMMIT_SIZE,\n+\t\t\t\t\t\t\t    stats->objects.disk_sizes.commits),\n+\t\t\t      value_delim);\n \tprint_keyvalue(\"objects.trees.disk_size\", key_delim,\n \t\t       stats->objects.disk_sizes.trees, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.trees.disk_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_TREE_SIZE,\n+\t\t\t\t\t\t\t    stats->objects.disk_sizes.trees),\n+\t\t\t      value_delim);\n \tprint_keyvalue(\"objects.blobs.disk_size\", key_delim,\n \t\t       stats->objects.disk_sizes.blobs, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.blobs.blob_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_BLOB_SIZE,\n+\t\t\t\t\t\t\t    stats->objects.disk_sizes.blobs),\n+\t\t\t      value_delim);\n \tprint_keyvalue(\"objects.tags.disk_size\", key_delim,\n \t\t       stats->objects.disk_sizes.tags, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.tags.disk_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_TAG_SIZE,\n+\t\t\t\t\t\t\t    stats->objects.disk_sizes.tags),\n+\t\t\t      value_delim);\n \n \tprint_object_data(\"objects.commits.max_size\", key_delim,\n \t\t\t  &stats->objects.largest.commit_size, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.commits.max_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_COMMIT_LARGEST,\n+\t\t\t\t\t\t\t    stats->objects.largest.commit_size.value),\n+\t\t\t      value_delim);\n \tprint_object_data(\"objects.trees.max_size\", key_delim,\n \t\t\t  &stats->objects.largest.tree_size, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.trees.max_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_TREE_LARGEST,\n+\t\t\t\t\t\t\t    stats->objects.largest.tree_size.value),\n+\t\t\t      value_delim);\n \tprint_object_data(\"objects.blobs.max_size\", key_delim,\n \t\t\t  &stats->objects.largest.blob_size, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.blobs.max_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_BLOB_LARGEST,\n+\t\t\t\t\t\t\t    stats->objects.largest.blob_size.value),\n+\t\t\t      value_delim);\n \tprint_object_data(\"objects.tags.max_size\", key_delim,\n \t\t\t  &stats->objects.largest.tag_size, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.tags.max_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(CONCERN_STAT_TAG_LARGEST,\n+\t\t\t\t\t\t\t    stats->objects.largest.tag_size.value),\n+\t\t\t      value_delim);\n \n \tprint_object_data(\"objects.commits.max_parents\", key_delim,\n \t\t\t  &stats->objects.largest.parent_count, value_delim);\n@@ -935,6 +1225,8 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,\n \tstruct repo_structure stats = { 0 };\n \tstruct rev_info revs;\n \tint show_progress = -1;\n+\tint show_concern = 0;\n+\n \tstruct option options[] = {\n \t\tOPT_CALLBACK_F(0, \"format\", &format, N_(\"format\"),\n \t\t\t       N_(\"output format\"),\n@@ -944,6 +1236,7 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,\n \t\t\t       PARSE_OPT_NONEG | PARSE_OPT_NOARG,\n \t\t\t       parse_format_cb),\n \t\tOPT_BOOL(0, \"progress\", &show_progress, N_(\"show progress\")),\n+\t\tOPT_BOOL(0, \"concern\", &show_concern, N_(\"show level of concern\")),\n \t\tOPT_END()\n \t};\n \n@@ -962,13 +1255,13 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,\n \tswitch (format) {\n \tcase FORMAT_TABLE:\n \t\tstats_table_setup_structure(&table, &stats);\n-\t\tstats_table_print_structure(&table);\n+\t\tstats_table_print_structure(&table, show_concern);\n \t\tbreak;\n \tcase FORMAT_NEWLINE_TERMINATED:\n-\t\tstructure_keyvalue_print(&stats, '=', '\\n');\n+\t\tstructure_keyvalue_print(&stats, '=', '\\n', show_concern);\n \t\tbreak;\n \tcase FORMAT_NUL_TERMINATED:\n-\t\tstructure_keyvalue_print(&stats, '\\n', '\\0');\n+\t\tstructure_keyvalue_print(&stats, '\\n', '\\0', show_concern);\n \t\tbreak;\n \tdefault:\n \t\tBUG(\"invalid output format\");\ndiff --git a/sideband.c b/sideband.c\nindex 374187402e..0e75ab2317 100644\n--- a/sideband.c\n+++ b/sideband.c\n@@ -1,4 +1,4 @@\n-#define USE_THE_REPOSITORY_VARIABLE\n+define USE_THE_REPOSITORY_VARIABLE\n #define DISABLE_SIGN_COMPARE_WARNINGS\n \n #include \"git-compat-util.h\"\ndiff --git a/t/t1901-repo-structure.sh b/t/t1901-repo-structure.sh\nindex 02cc2b594a..f1e3fd2a75 100755\n--- a/t/t1901-repo-structure.sh\n+++ b/t/t1901-repo-structure.sh\n@@ -67,77 +67,7 @@ test_expect_success 'empty repository' '\n \t\tEOF\n \n \t\tgit repo structure >out 2>err &&\n-\n-\t\ttest_cmp expect out &&\n-\t\ttest_line_count = 0 err\n-\t)\n-'\n-\n-test_expect_success SHA1 'repository with references and objects' '\n-\ttest_when_finished \"rm -rf repo\" &&\n-\tgit init repo &&\n-\t(\n-\t\tcd repo &&\n-\t\ttest_commit_bulk 1005 &&\n-\t\tgit tag -a foo -m bar &&\n-\n-\t\toid=\"$(git rev-parse HEAD)\" &&\n-\t\tgit update-ref refs/remotes/origin/foo \"$oid\" &&\n-\n-\t\t# Also creates a commit, tree, and blob.\n-\t\tgit notes add -m foo &&\n-\n-\t\t# The tags disk size is handled specially due to the\n-\t\t# git-rev-list(1) --disk-usage=human option printing the full\n-\t\t# \"byte/bytes\" unit string instead of just \"B\".\n-\t\tcat >expect <<-EOF &&\n-\t\t| Repository structure      | Value      |\n-\t\t| ------------------------- | ---------- |\n-\t\t| * References              |            |\n-\t\t|   * Count                 |      4     |\n-\t\t|     * Branches            |      1     |\n-\t\t|     * Tags                |      1     |\n-\t\t|     * Remotes             |      1     |\n-\t\t|     * Others              |      1     |\n-\t\t|                           |            |\n-\t\t| * Reachable objects       |            |\n-\t\t|   * Count                 |   3.02 k   |\n-\t\t|     * Commits             |   1.01 k   |\n-\t\t|     * Trees               |   1.01 k   |\n-\t\t|     * Blobs               |   1.01 k   |\n-\t\t|     * Tags                |      1     |\n-\t\t|   * Inflated size         |  16.03 MiB |\n-\t\t|     * Commits             | 217.92 KiB |\n-\t\t|     * Trees               |  15.81 MiB |\n-\t\t|     * Blobs               |  11.68 KiB |\n-\t\t|     * Tags                |    132 B   |\n-\t\t|   * Disk size             | $(object_type_disk_usage all true) |\n-\t\t|     * Commits             | $(object_type_disk_usage commit true) |\n-\t\t|     * Trees               | $(object_type_disk_usage tree true) |\n-\t\t|     * Blobs               |  $(object_type_disk_usage blob true) |\n-\t\t|     * Tags                |    $(object_type_disk_usage tag) B   |\n-\t\t|                           |            |\n-\t\t| * Largest objects         |            |\n-\t\t|   * Commits               |            |\n-\t\t|     * Maximum size    [1] |    223 B   |\n-\t\t|     * Maximum parents [2] |      1     |\n-\t\t|   * Trees                 |            |\n-\t\t|     * Maximum size    [3] |  32.29 KiB |\n-\t\t|     * Maximum entries [4] |   1.01 k   |\n-\t\t|   * Blobs                 |            |\n-\t\t|     * Maximum size    [5] |     13 B   |\n-\t\t|   * Tags                  |            |\n-\t\t|     * Maximum size    [6] |    132 B   |\n-\n-\t\t[1] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a\n-\t\t[2] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a\n-\t\t[3] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c\n-\t\t[4] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c\n-\t\t[5] 97d808e45116bf02103490294d3d46dad7a2ac62\n-\t\t[6] 4dae4f5954f5e6feb3577cfb1b181daa3fd3afd2\n-\t\tEOF\n-\n-\t\tgit repo structure >out 2>err &&\n+\t\tcp out /tmp/test1-out &&\n \n \t\ttest_cmp expect out &&\n \t\ttest_line_count = 0 err\n@@ -204,6 +134,59 @@ test_expect_success SHA1 'lines and nul format' '\n \t)\n '\n \n+test_expect_success SHA1 'lines format with concerns' '\n+\ttest_when_finished \"rm -rf repo\" &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\t# configure thresholds to get concern indications\n+\t\tgit config repo.structure.concern.commit-count 7 &&\n+\t\tgit config repo.structure.concern.commit-size 14 &&\n+\t\tgit config repo.structure.concern.tree-size 10 &&\n+\t\tgit config repo.structure.concern.blob-size 8 &&\n+\t\tgit config repo.structure.concern.blob-inflated-size 10 &&\n+\t\tgit config repo.structure.concern.tag-size 2 &&\n+\t\tgit config repo.structure.concern.commit-largest 4 &&\n+\t\tgit config repo.structure.concern.blob-largest 2 &&\n+\t\ttouch configured &&\n+\t\ttest_commit_bulk 1005 &&\n+\t\tgit tag -a foo -m bar &&\n+\t\toid=\"$(git rev-parse HEAD)\" &&\n+\t\tgit update-ref refs/remotes/origin/foo \"$oid\" &&\n+\n+\t\t# Also creates a commit, tree, and blob.\n+\t\tgit notes add -m foo &&\n+\n+\n+\t\t# The tags disk size is handled specially due to the\n+\t\t# git-rev-list(1) --disk-usage=human option printing the full\n+\t\t# \"byte/bytes\" unit string instead of just \"B\".\n+\t\tcat >expect <<-EOF &&\n+\t\tobjects.commits.count.concern_level=2\n+\t\tobjects.trees.count.concern_level=0\n+\t\tobjects.blobs.count.concern_level=0\n+\t\tobjects.tags.count.concern_level=0\n+\t\tobjects.commits.inflated_size.concern_level=0\n+\t\tobjects.trees.inflated_size.concern_level=0\n+\t\tobjects.blobs.inflated_size.concern_level=3\n+\t\tobjects.tags.inflated_size.concern_level=0\n+\t\tobjects.commits.disk_size.concern_level=3\n+\t\tobjects.trees.disk_size.concern_level=8\n+\t\tobjects.blobs.blob_size.concern_level=6\n+\t\tobjects.tags.disk_size.concern_level=4\n+\t\tobjects.commits.max_size.concern_level=3\n+\t\tobjects.trees.max_size.concern_level=0\n+\t\tobjects.blobs.max_size.concern_level=1\n+\t\tobjects.tags.max_size.concern_level=0\n+\t\tEOF\n+\n+\t\tgit repo structure --concern --format=lines >actual 2>err &&\n+\t\ttest_grep \"concern_level\" actual >actual-concerns &&\n+\t\ttest_cmp expect actual-concerns &&\n+\t\ttest_line_count = 0 err\n+\t)\n+'\n+\n test_expect_success 'progress meter option' '\n \ttest_when_finished \"rm -rf repo\" &&\n \tgit init repo &&\n\n-- \n2.53.0\n\n\n"},{"id":"556524","messageId":"20261027-b4-repo-structure-concern-v3-2-98d7a5704170@fastmail.com","threadId":"66621","inReplyTo":"20261027-b4-repo-structure-concern-v3-0-98d7a5704170@fastmail.com","subject":"[PATCH v3 2/2] repo: make concern thresholds configurable","fromName":"Mark C. Chu-Carroll","fromEmail":"markchucarroll@fastmail.com","sentAt":"2026-10-27T15:39:30Z","receivedAt":"2026-10-27T15:39:30Z","isPatch":true,"sender":{"key":"markchucarroll@fastmail.com","avatar":null},"body":"The concern reporting mechanism so far provides a\nmeasure of concern based on heuristic values estimated\nby analyzing a collection of (hopefully) representative\nrepositories. But for different projects/usecases, the\nthresholds at which a value becomes concerning may vary.\n\nProvide a mechanism using git config to allow users to\nconfigure per-repo concern levels for \"git repo structure\"\nconcern reporting.\n---\n Documentation/config/repo.adoc |  59 ++++++++++++++\n Documentation/git-repo.adoc    |  11 +++\n builtin/repo.c                 |  62 +++++++-------\n sideband.c                     |   2 +-\n t/t1901-repo-structure.sh      | 180 ++++++++++++++++++++++++++++++++++++++---\n 5 files changed, 273 insertions(+), 41 deletions(-)\n\ndiff --git a/Documentation/config/repo.adoc b/Documentation/config/repo.adoc\nnew file mode 100644\nindex 0000000000..811b099d72\n--- /dev/null\n+++ b/Documentation/config/repo.adoc\n@@ -0,0 +1,59 @@\n+`repo.concern.commit-count`::\n+\tSets the value of the concern threshold for commit count.\n+\n+`repo.concern.tree-count`::\n+\tSets the value of the concern threshold for tree count.\n+\n+`repo.concern.blob-count`::\n+\tSets the value of the concern threshold for blob count.\n+\n+`repo.concern.tag-count`::\n+\tSets the value of the concern threshold for tag count.\n+\n+`repo.concern.commit-size`::\n+\tSets the value of the concern threshold for total commit size.\n+\n+`repo.concern.tree-size`::\n+\tSets the value of the concern threshold for total tree size.\n+\n+`repo.concern.blob-size`::\n+\tSets the value of the concern threshold for total blob size.\n+\n+`repo.concern.tag-size`::\n+\tSets the value of the concern threshold for total tag size.\n+\n+`repo.concern.commit-inflated-size`::\n+\tSets the value of the concern threshold for total size\n+\tof all inflated commits.\n+\n+`repo.concern.tree-inflated-size`::\n+\tSets the value of the concern threshold for total size of\n+\tall inflated trees.\n+\n+`repo.concern.blob-inflated-size`::\n+\tSets the value of the concern threshold for total size of\n+\tall inflated blobs.\n+\n+`repo.concern.tag-inflated-size`::\n+\tSets the value of the concern threshold for the total size\n+\tof all inflated tags.\n+\n+`repo.concern.commit-largest`::\n+\tSets the value of the concern threshold the size of the largest\n+\tcommit.\n+\n+`repo.concern.tree-largest`::\n+\tSets the value of the concern threshold for the size of the\n+\tlargest tree.\n+\n+`repo.concern.blob-largest`::\n+\tSets the value of the concern threshold for the size of the\n+\tlargest blob.\n+\n+`repo.concern.tag-largest`::\n+\tSets the value of the concern threshold for the size of the\n+\tlargest tag.\n+\n+All of the concern thresholds are integer values for the\n+floor of the base-2 log of the threshold value. The default values\n+of each threshold are set to heuristically determined values.\ndiff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc\nindex f186b27d87..516403e17d 100644\n--- a/Documentation/git-repo.adoc\n+++ b/Documentation/git-repo.adoc\n@@ -101,6 +101,8 @@ statistic value. The value is considered concerning if it exceeds a heuristicall\n chosen threshold value; the degree of concern is the difference between the\n log_2 of the value, and the log_2 of the corresponding threshold.\n \n+The threshold values are configurable; see the configuration section below.\n+\n INFO KEYS\n ---------\n In order to obtain a set of values from `git repo info`, you should provide\n@@ -153,6 +155,15 @@ using the `nul` format:\n git repo info --format=nul layout.bare layout.shallow\n ------------\n \n+CONFIGURATION\n+-------------\n+\n+include::includes/cmd-config-section-all.adoc[]\n+\n+:git-repo: 1\n+include::config/repo.adoc[]\n+\n+\n SEE ALSO\n --------\n linkgit:git-rev-parse[1]\ndiff --git a/builtin/repo.c b/builtin/repo.c\nindex a450497d4e..ed579e6685 100644\n--- a/builtin/repo.c\n+++ b/builtin/repo.c\n@@ -2,6 +2,7 @@\n \n #include \"builtin.h\"\n #include \"commit.h\"\n+#include \"config.h\"\n #include \"environment.h\"\n #include \"hash.h\"\n #include \"hex.h\"\n@@ -29,6 +30,8 @@\n #define REPO_STRUCTURE_USAGE \\\n \t\"git repo structure [--format=(table|lines|nul) | -z] [--[no-]concern]\"\n \n+#define REPO_STRUCTURE_CONFIG_PREFIX \"repo.concern.\"\n+\n static const char *const repo_usage[] = {\n \tREPO_INFO_USAGE,\n \tREPO_STRUCTURE_USAGE,\n@@ -386,40 +389,43 @@ enum concern_stat {\n \tCONCERN_STAT_TREE_INFLATED_SIZE,\n \tCONCERN_STAT_BLOB_INFLATED_SIZE,\n \tCONCERN_STAT_TAG_INFLATED_SIZE,\n+\tCONCERN_STAT_MAXIMUM\n };\n \n-struct concern_stat_threshold repo_structure_concern_thresholds[] = {\n-\t{ .name = \"commit_count\", .threshold = 19 },\n-\t{.name = \"tree_count\", .threshold = 21 },\n-\t{ .name = \"blob_count\", .threshold = 22 },\n-\t{ .name = \"tag_count\", .threshold = 15 },\n-\t{ .name = \"commit_size\", .threshold = 28 },\n-\t{ .name = \"tree_size\", .threshold = 27 },\n-\t{ .name = \"blob_size\", .threshold = 36 },\n-\t{ .name = \"tags_size\", .threshold = 18 },\n-\t{ .name = \"commit_largest\", .threshold = 18  },\n-\t{ .name = \"tree_largest\", .threshold = 18 },\n-\t{ .name = \"blob_largest\", .threshold = 26 },\n-\t{ .name = \"tag_largest\", .threshold =  15 },\n-\t{ .name = \"commit_inflated_size\", .threshold = 28 },\n-\t{ .name = \"tree_inflated_size\", .threshold = 32 },\n-\t{ .name = \"blob_inflated_size\", .threshold = 34 },\n-\t{ .name = \"tag_inflated_size\", .threshold = 25}\n+struct concern_stat_threshold concern_stat_thresholds[] = {\n+\t{ .name = \"commit-count\", .threshold = 19 },\n+\t{ .name = \"tree-count\", .threshold = 21 },\n+\t{ .name = \"blob-count\", .threshold = 22 },\n+\t{ .name = \"tag-count\", .threshold = 15 },\n+\t{ .name = \"commit-size\", .threshold = 28 },\n+\t{ .name = \"tree-size\", .threshold = 27 },\n+\t{ .name = \"blob-size\", .threshold = 36 },\n+\t{ .name = \"tag-size\", .threshold = 18 },\n+\t{ .name = \"commit-largest\", .threshold = 18  },\n+\t{ .name = \"tree-largest\", .threshold = 18 },\n+\t{ .name = \"blob-largest\", .threshold = 26 },\n+\t{ .name = \"tag-largest\", .threshold =  15 },\n+\t{ .name = \"commit-inflated-size\", .threshold = 28 },\n+\t{ .name = \"tree-inflated-size\", .threshold = 32 },\n+\t{ .name = \"blob-inflated-size\", .threshold = 34 },\n+\t{ .name = \"tag-inflated-size\", .threshold = 25},\n+\t{ 0 }\n };\n \n /*\n  * Concern thresholds aren't universal. We try to set reasonable default\n  * values, but projects may want to set these thresholds differently. To\n  * support that, they can set the thresholds for their project in the\n- * project's git config, in the section \"repo.structure.concern\".\n+ * project's git config, in the section \"repo.concern\".\n   */\n static int config_concern_thresholds(const char *prefixed_var,\n \t\t\t\t     const char *str_val,\n \t\t\t\t     const struct config_context *ctx,\n \t\t\t\t     void *cb UNUSED) {\n-\tconst char *config_prefix = \"repo.structure.concern.\";\n-\tif (!strncmp(config_prefix, prefixed_var, strlen(config_prefix))) {\n-\t\tconst char *concern_name = &prefixed_var[strlen(config_prefix)];\n+\tif (!strncmp(REPO_STRUCTURE_CONFIG_PREFIX, prefixed_var,\n+\t      strlen(REPO_STRUCTURE_CONFIG_PREFIX))) {\n+\t\tconst char *concern_name =\n+\t\t\t&prefixed_var[strlen(REPO_STRUCTURE_CONFIG_PREFIX)];\n \t\tfor (int i = 0; concern_stat_thresholds[i].name != 0; i++) {\n \t\t\tif (!strcmp(concern_stat_thresholds[i].name, concern_name)) {\n \t\t\t\tint value = git_config_int(prefixed_var, str_val, ctx->kvi);\n@@ -435,7 +441,7 @@ static int config_concern_thresholds(const char *prefixed_var,\n \treturn 0;\n }\n \n-static size_t approx_log2(long value) {\n+static size_t approx_log2(size_t value) {\n \tint position = 0;\n \tif (value <= 0) {\n \t\treturn 0;\n@@ -450,7 +456,7 @@ static size_t approx_log2(long value) {\n static size_t repo_structure_concern_level(enum concern_stat stat_id,\n \t\t\t\t\t   size_t value) {\n \tsize_t log_value;\n-\tif (stat_id >= ARRAY_SIZE(concern_stat_thresholds))\n+\tif (stat_id >= CONCERN_STAT_MAXIMUM)\n \t\tBUG(\"unexpected repository stat type\");\n \tlog_value = approx_log2(value);\n \tif (log_value > concern_stat_thresholds[stat_id].threshold)\n@@ -844,6 +850,7 @@ static void stats_table_print_structure(const struct stats_table *table,\n \t\t\tstrbuf_utf8_align(&buf, ALIGN_LEFT, concern_col_width, concern_str.buf);\n \t\t\tstrbuf_addstr(&buf, \" |\");\n \t\t}\n+\t\tstrbuf_release(&concern_str);\n \t\tprintf(\"%s\\n\", buf.buf);\n \t}\n \n@@ -977,7 +984,7 @@ static void structure_keyvalue_print(struct repo_structure *stats,\n \tprint_keyvalue(\"objects.blobs.disk_size\", key_delim,\n \t\t       stats->objects.disk_sizes.blobs, value_delim);\n \tif (show_concern)\n-\t\tprint_keyvalue(\"objects.blobs.blob_size.concern_level\", key_delim,\n+\t\tprint_keyvalue(\"objects.blobs.disk_size.concern_level\", key_delim,\n \t\t\t       repo_structure_concern_level(CONCERN_STAT_BLOB_SIZE,\n \t\t\t\t\t\t\t    stats->objects.disk_sizes.blobs),\n \t\t\t      value_delim);\n@@ -997,7 +1004,7 @@ static void structure_keyvalue_print(struct repo_structure *stats,\n \t\t\t\t\t\t\t    stats->objects.largest.commit_size.value),\n \t\t\t      value_delim);\n \tprint_object_data(\"objects.trees.max_size\", key_delim,\n-\t\t\t  &stats->objects.largest.tree_size, value_delim);\n+\t\t\t &stats->objects.largest.tree_size, value_delim);\n \tif (show_concern)\n \t\tprint_keyvalue(\"objects.trees.max_size.concern_level\", key_delim,\n \t\t\t       repo_structure_concern_level(CONCERN_STAT_TREE_LARGEST,\n@@ -1011,7 +1018,7 @@ static void structure_keyvalue_print(struct repo_structure *stats,\n \t\t\t\t\t\t\t    stats->objects.largest.blob_size.value),\n \t\t\t      value_delim);\n \tprint_object_data(\"objects.tags.max_size\", key_delim,\n-\t\t\t  &stats->objects.largest.tag_size, value_delim);\n+\t\t\t   &stats->objects.largest.tag_size, value_delim);\n \tif (show_concern)\n \t\tprint_keyvalue(\"objects.tags.max_size.concern_level\", key_delim,\n \t\t\t       repo_structure_concern_level(CONCERN_STAT_TAG_LARGEST,\n@@ -1243,7 +1250,8 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,\n \targc = parse_options(argc, argv, prefix, options, repo_structure_usage, 0);\n \tif (argc)\n \t\tusage(_(\"too many arguments\"));\n-\n+\tif (show_concern)\n+\t\trepo_config(repo, &config_concern_thresholds, NULL);\n \trepo_init_revisions(repo, &revs, prefix);\n \n \tif (show_progress < 0)\ndiff --git a/sideband.c b/sideband.c\nindex 0e75ab2317..374187402e 100644\n--- a/sideband.c\n+++ b/sideband.c\n@@ -1,4 +1,4 @@\n-define USE_THE_REPOSITORY_VARIABLE\n+#define USE_THE_REPOSITORY_VARIABLE\n #define DISABLE_SIGN_COMPARE_WARNINGS\n \n #include \"git-compat-util.h\"\ndiff --git a/t/t1901-repo-structure.sh b/t/t1901-repo-structure.sh\nindex f1e3fd2a75..e8f1269ad4 100755\n--- a/t/t1901-repo-structure.sh\n+++ b/t/t1901-repo-structure.sh\n@@ -67,13 +67,166 @@ test_expect_success 'empty repository' '\n \t\tEOF\n \n \t\tgit repo structure >out 2>err &&\n-\t\tcp out /tmp/test1-out &&\n \n \t\ttest_cmp expect out &&\n \t\ttest_line_count = 0 err\n \t)\n '\n \n+test_expect_success SHA1 'repository with references and objects' '\n+\ttest_when_finished \"rm -rf repo\" &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\ttest_commit_bulk 1005 &&\n+\t\tgit tag -a foo -m bar &&\n+\n+\t\toid=\"$(git rev-parse HEAD)\" &&\n+\t\tgit update-ref refs/remotes/origin/foo \"$oid\" &&\n+\n+\t\t# Also creates a commit, tree, and blob.\n+\t\tgit notes add -m foo &&\n+\n+\t\t# The tags disk size is handled specially due to the\n+\t\t# git-rev-list(1) --disk-usage=human option printing the full\n+\t\t# \"byte/bytes\" unit string instead of just \"B\".\n+\t\tcat >expect <<-EOF &&\n+\t\t| Repository structure      | Value      |\n+\t\t| ------------------------- | ---------- |\n+\t\t| * References              |            |\n+\t\t|   * Count                 |      4     |\n+\t\t|     * Branches            |      1     |\n+\t\t|     * Tags                |      1     |\n+\t\t|     * Remotes             |      1     |\n+\t\t|     * Others              |      1     |\n+\t\t|                           |            |\n+\t\t| * Reachable objects       |            |\n+\t\t|   * Count                 |   3.02 k   |\n+\t\t|     * Commits             |   1.01 k   |\n+\t\t|     * Trees               |   1.01 k   |\n+\t\t|     * Blobs               |   1.01 k   |\n+\t\t|     * Tags                |      1     |\n+\t\t|   * Inflated size         |  16.03 MiB |\n+\t\t|     * Commits             | 217.92 KiB |\n+\t\t|     * Trees               |  15.81 MiB |\n+\t\t|     * Blobs               |  11.68 KiB |\n+\t\t|     * Tags                |    132 B   |\n+\t\t|   * Disk size             | $(object_type_disk_usage all true) |\n+\t\t|     * Commits             | $(object_type_disk_usage commit true) |\n+\t\t|     * Trees               | $(object_type_disk_usage tree true) |\n+\t\t|     * Blobs               |  $(object_type_disk_usage blob true) |\n+\t\t|     * Tags                |    $(object_type_disk_usage tag) B   |\n+\t\t|                           |            |\n+\t\t| * Largest objects         |            |\n+\t\t|   * Commits               |            |\n+\t\t|     * Maximum size    [1] |    223 B   |\n+\t\t|     * Maximum parents [2] |      1     |\n+\t\t|   * Trees                 |            |\n+\t\t|     * Maximum size    [3] |  32.29 KiB |\n+\t\t|     * Maximum entries [4] |   1.01 k   |\n+\t\t|   * Blobs                 |            |\n+\t\t|     * Maximum size    [5] |     13 B   |\n+\t\t|   * Tags                  |            |\n+\t\t|     * Maximum size    [6] |    132 B   |\n+\n+\t\t[1] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a\n+\t\t[2] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a\n+\t\t[3] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c\n+\t\t[4] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c\n+\t\t[5] 97d808e45116bf02103490294d3d46dad7a2ac62\n+\t\t[6] 4dae4f5954f5e6feb3577cfb1b181daa3fd3afd2\n+\t\tEOF\n+\n+\t\tgit repo structure >out 2>err &&\n+\n+\t\ttest_cmp expect out &&\n+\t\ttest_line_count = 0 err\n+\t)\n+'\n+\n+test_expect_success SHA1 'concern flagging' '\n+\ttest_when_finished \"rm -rf repo\" &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\t# differences between zlib and zlib-ng can cause this\n+\t\t# test to fail; so we just turn it off.\n+\t\tgit config core.compression 0 &&\n+\t\t# configure thresholds to get concern indications\n+\t\tgit config repo.concern.commit-count 7 &&\n+\t\tgit config repo.concern.commit-size 14 &&\n+\t\tgit config repo.concern.tree-size 10 &&\n+\t\tgit config repo.concern.blob-size 8 &&\n+\t\tgit config repo.concern.blob-inflated-size 10 &&\n+\t\tgit config repo.concern.tag-size 2 &&\n+\t\tgit config repo.concern.commit-largest 4 &&\n+\t\tgit config repo.concern.blob-largest 2 &&\n+\t\ttest_commit_bulk 1005 &&\n+\t\tgit tag -a foo -m bar &&\n+\t\toid=\"$(git rev-parse HEAD)\" &&\n+\t\tgit update-ref refs/remotes/origin/foo \"$oid\" &&\n+\n+\t\t# Also creates a commit, tree, and blob.\n+\t\tgit notes add -m foo &&\n+\n+\n+\t\t# The tags disk size is handled specially due to the\n+\t\t# git-rev-list(1) --disk-usage=human option printing the full\n+\t\t# \"byte/bytes\" unit string instead of just \"B\".\n+\t\tcat >expect <<-EOF &&\n+\t\t| Repository structure      | Value      | Concern  |\n+\t\t| ------------------------- | ---------- | -------- |\n+\t\t| * References              |            |          |\n+\t\t|   * Count                 |      4     |          |\n+\t\t|     * Branches            |      1     |          |\n+\t\t|     * Tags                |      1     |          |\n+\t\t|     * Remotes             |      1     |          |\n+\t\t|     * Others              |      1     |          |\n+\t\t|                           |            |          |\n+\t\t| * Reachable objects       |            |          |\n+\t\t|   * Count                 |   3.02 k   |          |\n+\t\t|     * Commits             |   1.01 k   | **       |\n+\t\t|     * Trees               |   1.01 k   |          |\n+\t\t|     * Blobs               |   1.01 k   |          |\n+\t\t|     * Tags                |      1     |          |\n+\t\t|   * Inflated size         |  16.03 MiB |          |\n+\t\t|     * Commits             | 217.92 KiB |          |\n+\t\t|     * Trees               |  15.81 MiB |          |\n+\t\t|     * Blobs               |  11.68 KiB | ***      |\n+\t\t|     * Tags                |    132 B   |          |\n+\t\t|   * Disk size             | $(object_type_disk_usage all true) |          |\n+\t\t|     * Commits             | $(object_type_disk_usage commit true) | ***      |\n+\t\t|     * Trees               | $(object_type_disk_usage tree true) | ******** |\n+\t\t|     * Blobs               |  $(object_type_disk_usage blob true) | ******   |\n+\t\t|     * Tags                |    $(object_type_disk_usage tag) B   | *****    |\n+\t\t|                           |            |          |\n+\t\t| * Largest objects         |            |          |\n+\t\t|   * Commits               |            |          |\n+\t\t|     * Maximum size    [1] |    223 B   | ***      |\n+\t\t|     * Maximum parents [2] |      1     |          |\n+\t\t|   * Trees                 |            |          |\n+\t\t|     * Maximum size    [3] |  32.29 KiB |          |\n+\t\t|     * Maximum entries [4] |   1.01 k   |          |\n+\t\t|   * Blobs                 |            |          |\n+\t\t|     * Maximum size    [5] |     13 B   | *        |\n+\t\t|   * Tags                  |            |          |\n+\t\t|     * Maximum size    [6] |    132 B   |          |\n+\n+\t\t[1] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a\n+\t\t[2] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a\n+\t\t[3] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c\n+\t\t[4] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c\n+\t\t[5] 97d808e45116bf02103490294d3d46dad7a2ac62\n+\t\t[6] 4dae4f5954f5e6feb3577cfb1b181daa3fd3afd2\n+\t\tEOF\n+\n+\t\tgit repo structure --concern >actual 2>err &&\n+\n+\t\ttest_cmp expect actual &&\n+\t\ttest_line_count = 0 err\n+\t)\n+'\n+\n test_expect_success SHA1 'lines and nul format' '\n \ttest_when_finished \"rm -rf repo\" &&\n \tgit init repo &&\n@@ -139,16 +292,18 @@ test_expect_success SHA1 'lines format with concerns' '\n \tgit init repo &&\n \t(\n \t\tcd repo &&\n+\t\t# differences between zlib and zlib-ng can cause this\n+\t\t# test to fail; so we just turn it off.\n+\t\tgit config core.compression 0 &&\n \t\t# configure thresholds to get concern indications\n-\t\tgit config repo.structure.concern.commit-count 7 &&\n-\t\tgit config repo.structure.concern.commit-size 14 &&\n-\t\tgit config repo.structure.concern.tree-size 10 &&\n-\t\tgit config repo.structure.concern.blob-size 8 &&\n-\t\tgit config repo.structure.concern.blob-inflated-size 10 &&\n-\t\tgit config repo.structure.concern.tag-size 2 &&\n-\t\tgit config repo.structure.concern.commit-largest 4 &&\n-\t\tgit config repo.structure.concern.blob-largest 2 &&\n-\t\ttouch configured &&\n+\t\tgit config repo.concern.commit-count 7 &&\n+\t\tgit config repo.concern.commit-size 14 &&\n+\t\tgit config repo.concern.tree-size 10 &&\n+\t\tgit config repo.concern.blob-size 8 &&\n+\t\tgit config repo.concern.blob-inflated-size 10 &&\n+\t\tgit config repo.concern.tag-size 2 &&\n+\t\tgit config repo.concern.commit-largest 4 &&\n+\t\tgit config repo.concern.blob-largest 2 &&\n \t\ttest_commit_bulk 1005 &&\n \t\tgit tag -a foo -m bar &&\n \t\toid=\"$(git rev-parse HEAD)\" &&\n@@ -157,7 +312,6 @@ test_expect_success SHA1 'lines format with concerns' '\n \t\t# Also creates a commit, tree, and blob.\n \t\tgit notes add -m foo &&\n \n-\n \t\t# The tags disk size is handled specially due to the\n \t\t# git-rev-list(1) --disk-usage=human option printing the full\n \t\t# \"byte/bytes\" unit string instead of just \"B\".\n@@ -172,8 +326,8 @@ test_expect_success SHA1 'lines format with concerns' '\n \t\tobjects.tags.inflated_size.concern_level=0\n \t\tobjects.commits.disk_size.concern_level=3\n \t\tobjects.trees.disk_size.concern_level=8\n-\t\tobjects.blobs.blob_size.concern_level=6\n-\t\tobjects.tags.disk_size.concern_level=4\n+\t\tobjects.blobs.disk_size.concern_level=6\n+\t\tobjects.tags.disk_size.concern_level=5\n \t\tobjects.commits.max_size.concern_level=3\n \t\tobjects.trees.max_size.concern_level=0\n \t\tobjects.blobs.max_size.concern_level=1\n\n-- \n2.53.0\n\n\n"},{"id":"556659","messageId":"637732be5efcc579fa737004ca4132bc@fastmail.com","threadId":"66621","inReplyTo":"auC7YzhTbn3q7GPt@pks.im","subject":"Re: [PATCH v2 3/3] repo: add documentation for level-of-concern.","fromName":"Mark C. Chu-Carroll","fromEmail":"markchucarroll@fastmail.com","sentAt":"2026-10-28T13:23:38Z","receivedAt":"2026-10-28T13:23:38Z","isPatch":true,"sender":{"key":"markchucarroll@fastmail.com","avatar":null},"body":"\nOn Tue, 27 Oct 2026 15:55:31 +0100, Patrick Steinhardt <ps@pks.im> wrote:\n> On Mon, Oct 26, 2026 at 01:43:56PM -0400, Mark C. Chu-Carroll wrote:\n> > ---\n> >  Documentation/config/repo.adoc | 59 ++++++++++++++++++++++++++++++++++++++++++\n> >  Documentation/git-repo.adoc    | 27 +++++++++++++++++--\n> >  2 files changed, 84 insertions(+), 2 deletions(-)\n> > \n> > diff --git a/Documentation/config/repo.adoc b/Documentation/config/repo.adoc\n> > new file mode 100644\n> > index 0000000000..811b099d72\n> > --- /dev/null\n> > +++ b/Documentation/config/repo.adoc\n> > @@ -0,0 +1,59 @@\n> > +`repo.concern.commit-count`::\n> > +\tSets the value of the concern threshold for commit count.\n> > +\n> > +`repo.concern.tree-count`::\n> > +\tSets the value of the concern threshold for tree count.\n> \n> I think this commit can be split and squashed into the other two\n> commits:\n> \n>   - The change to synopsis and documentation of \"--concern\" should be\n>     squashed into the first commit.\n> \n>   - These config values here should be squashed into the second commit.\n\n\nYeah, that makes sense. \n(Note, this will appear out of order; original send failed yesterday.)\n\n       -Mark\n\n"},{"id":"556806","messageId":"20261029-b4-repo-structure-concern-v4-0-d813537ec617@fastmail.com","threadId":"66621","inReplyTo":"20261021-b4-repo-structure-concern-v1-0-e3010552292c@fastmail.com","subject":"[PATCH v4 0/2] repo: add degree-of-concern reporting to structure","fromName":"Mark C. Chu-Carroll","fromEmail":"markchucarroll@fastmail.com","sentAt":"2026-10-29T17:52:21Z","receivedAt":"2026-10-29T17:52:21Z","isPatch":true,"sender":{"key":"markchucarroll@fastmail.com","avatar":null},"body":"\"git repo structure\" computes a set of statistics that are useful for\ndiagnosing performance issues in a git repository. But for a typical\nuser who hasn't encountered any issues yet, it can be difficult to\ninterpret the data - when should a value be considered concerning?\n\nThis patchset introduces a feature to \"git repo structure\" that reports\non how concerning certain measures of storage structure  are for a\nrepository. In table mode, concern is displayed as a column containing\na number of stars indicating the degree of concern; in line or nul mode,\nconcerns are displayed as additional lines with a numeric value. This \nprovides a functionality equivalent to the concern column provided \nby \"git-sizer\", although the implementation is different.\n\nTo select defaults for the thresholds where a value becomes concern,\na (hopefully) representative selection of repositories were analyzed.\nBased on empirical exploration of the data, the measurement that seemed\nclosest to accurately assessing when a repository's structure becomes\nconcerning was based on the base 2 logarithm of the value. Across all of\nthe sample repositories, for all of the measurements, the point at which\nthings become concerning was when a base 2 logarithm of a statistic's\nvalue was more than 1 standard deviation from the mean of the log_2\nvalues of that stastistic in the sample set.  Based on this, the concern\nthreshold was set for each of the measures.\n\nWhile this seems like a reasonable approach for setting default\nthresholds, it's obviously still very ad-hoc. It's likely that over\ntime, the default values will evolve; and in any case, some projects\nwill want to set their own thresholds. To allow that, this includes\nsupport for configuring the thresholds on a per-repository basis.\n\nOverview of the change:\n         \n* The first commit adds the command-line option \"--concern\", and creates the basic implementation of concern reporting.\n* The next commit adds configurability for the concern thresholds.\n\nSigned-off-by: Mark C. Chu-Carroll <markchucarroll@fastmail.com>\n---\nChanges in v4:\n- Reworked the history; the previous round had a history-rewrite error.\n- Removed the duplication of configuration keys for the concern thresholds.\n- Changed the prefix of the config keys.\n- readability/quality of life naming changes.\n- Link to v3: https://patch.msgid.link/20261027-b4-repo-structure-concern-v3-0-98d7a5704170@fastmail.com\n\nChanges in v3:\n- Merge the documentation commit backwards into the other two commits.\n- Link to v2: https://patch.msgid.link/20261026-b4-repo-structure-concern-v2-0-e0df34228dc8@fastmail.com\n\nChanges in v2:\n- Fix memory leak in concern string generation.\n- Fix CI/CD test issue involving zlib versions.\n- Updating the naming of the config value prefix.\n- Link to v1: https://patch.msgid.link/20261021-b4-repo-structure-concern-v1-0-e3010552292c@fastmail.com\n\nTo: git@vger.kernel.org\n\n---\nMark C. Chu-Carroll (2):\n      repo: add level of concern reporting\n      repo: make concern thresholds configurable\n\n Documentation/config/repo.adoc |  59 ++++++\n Documentation/git-repo.adoc    |  27 ++-\n builtin/repo.c                 | 408 ++++++++++++++++++++++++++++++++++++++---\n repo.c                         |   1 +\n t/t1901-repo-structure.sh      | 137 ++++++++++++++\n 5 files changed, 602 insertions(+), 30 deletions(-)\n\nRange-diff versus v3:\n\n1:  0f4e75e270 ! 1:  5f67be20e2 repo: add level of concern reporting\n    @@ Commit message\n         interpret the data - when should a value be considered\n         concerning?\n     \n    -    Add an option to \"git repo structure\" that adds a \"degree\n    -    of concern\" column to the structure report, which indicates\n    -    when a stat values has reached a point where it might be\n    -    concerning, and indicate how severe the concern should be.\n    +    Add a new option to \"git repo structure\" which causes it\n    +    to generate an additional column providing a level of concern\n    +    for many values.\n    +\n    +    To select defaults for the thresholds where a value becomes concern,\n    +    a (hopefully) representative selection of repositories were analyzed.\n    +    Based on empirical exploration of the data, the measurement that seemed\n    +    closest to accurately assessing when a repository's structure becomes\n    +    concerning was based on the base 2 logarithm of the value. Across all of\n    +    the sample repositories, for all of the measurements, the point at which\n    +    things become problematic was when a base 2 logarithm of a value was\n    +    more than 1 standard deviation from the mean of the log_2 values in\n    +    the sample set.\n    +\n    +    Signed-off-by: Mark C. Chu-Carroll <markchucarroll@fastmail.com>\n     \n      ## Documentation/git-repo.adoc ##\n     @@ Documentation/git-repo.adoc: SYNOPSIS\n    @@ Documentation/git-repo.adoc: supported:\n     \n      ## builtin/repo.c ##\n     @@\n    + #include \"tree.h\"\n    + #include \"tree-walk.h\"\n    + #include \"utf8.h\"\n    ++#include \"wrapper.h\"\n    + \n    + #define REPO_INFO_USAGE \\\n    + \t\"git repo info [--format=(lines|nul) | -z] [--all | <key>...]\", \\\n      \t\"git repo info --keys [--format=(lines|nul) | -z]\"\n      \n      #define REPO_STRUCTURE_USAGE \\\n    @@ builtin/repo.c: struct stats_table {\n     +\tsize_t threshold;\n     +};\n     +\n    -+enum concern_stat {\n    -+\tCONCERN_STAT_COMMIT_COUNT,\n    -+\tCONCERN_STAT_TREE_COUNT,\n    -+\tCONCERN_STAT_BLOB_COUNT,\n    -+\tCONCERN_STAT_TAG_COUNT,\n    -+\tCONCERN_STAT_COMMIT_SIZE,\n    -+\tCONCERN_STAT_TREE_SIZE,\n    -+\tCONCERN_STAT_BLOB_SIZE,\n    -+\tCONCERN_STAT_TAG_SIZE,\n    -+\tCONCERN_STAT_COMMIT_LARGEST,\n    -+\tCONCERN_STAT_TREE_LARGEST,\n    -+\tCONCERN_STAT_BLOB_LARGEST,\n    -+\tCONCERN_STAT_TAG_LARGEST,\n    -+\tCONCERN_STAT_COMMIT_INFLATED_SIZE,\n    -+\tCONCERN_STAT_TREE_INFLATED_SIZE,\n    -+\tCONCERN_STAT_BLOB_INFLATED_SIZE,\n    -+\tCONCERN_STAT_TAG_INFLATED_SIZE,\n    -+};\n    ++#define CONCERN_STAT_COMMIT_COUNT \"commit-count\"\n    ++#define CONCERN_STAT_TREE_COUNT \"tree-count\"\n    ++#define CONCERN_STAT_BLOB_COUNT \"blob-count\"\n    ++#define CONCERN_STAT_TAG_COUNT \"tag-count\"\n    ++#define CONCERN_STAT_COMMIT_SIZE \"commit-size\"\n    ++#define CONCERN_STAT_TREE_SIZE \"tree-size\"\n    ++#define CONCERN_STAT_BLOB_SIZE \"blob-size\"\n    ++#define CONCERN_STAT_TAG_SIZE \"tag-size\"\n    ++#define CONCERN_STAT_COMMIT_LARGEST \"commit-largest\"\n    ++#define CONCERN_STAT_TREE_LARGEST \"tree-largest\"\n    ++#define CONCERN_STAT_BLOB_LARGEST \"blob-largest\"\n    ++#define CONCERN_STAT_TAG_LARGEST \"tag-largest\"\n    ++#define CONCERN_STAT_COMMIT_INFLATED_SIZE \"commit-inflated-size\"\n    ++#define CONCERN_STAT_TREE_INFLATED_SIZE \"tree-inflated-size\"\n    ++#define CONCERN_STAT_BLOB_INFLATED_SIZE \"blob-inflated-size\"\n    ++#define CONCERN_STAT_TAG_INFLATED_SIZE \"tag-inflated-size\"\n     +\n     +struct concern_stat_threshold repo_structure_concern_thresholds[] = {\n    -+\t{ .name = \"commit_count\", .threshold = 19 },\n    -+\t{.name = \"tree_count\", .threshold = 21 },\n    -+\t{ .name = \"blob_count\", .threshold = 22 },\n    -+\t{ .name = \"tag_count\", .threshold = 15 },\n    -+\t{ .name = \"commit_size\", .threshold = 28 },\n    -+\t{ .name = \"tree_size\", .threshold = 27 },\n    -+\t{ .name = \"blob_size\", .threshold = 36 },\n    -+\t{ .name = \"tags_size\", .threshold = 18 },\n    -+\t{ .name = \"commit_largest\", .threshold = 18  },\n    -+\t{ .name = \"tree_largest\", .threshold = 18 },\n    -+\t{ .name = \"blob_largest\", .threshold = 26 },\n    -+\t{ .name = \"tag_largest\", .threshold =  15 },\n    -+\t{ .name = \"commit_inflated_size\", .threshold = 28 },\n    -+\t{ .name = \"tree_inflated_size\", .threshold = 32 },\n    -+\t{ .name = \"blob_inflated_size\", .threshold = 34 },\n    -+\t{ .name = \"tag_inflated_size\", .threshold = 25}\n    ++\t{ .name = CONCERN_STAT_COMMIT_COUNT, .threshold = 19 },\n    ++\t{ .name = CONCERN_STAT_TREE_COUNT, .threshold = 21 },\n    ++\t{ .name = CONCERN_STAT_BLOB_COUNT, .threshold = 22 },\n    ++\t{ .name = CONCERN_STAT_TAG_COUNT, .threshold = 15 },\n    ++\t{ .name = CONCERN_STAT_COMMIT_SIZE, .threshold = 28 },\n    ++\t{ .name = CONCERN_STAT_TREE_SIZE, .threshold = 27 },\n    ++\t{ .name = CONCERN_STAT_BLOB_SIZE, .threshold = 36 },\n    ++\t{ .name = CONCERN_STAT_TAG_SIZE, .threshold = 18 },\n    ++\t{ .name = CONCERN_STAT_COMMIT_LARGEST, .threshold = 18  },\n    ++\t{ .name = CONCERN_STAT_TREE_LARGEST, .threshold = 18 },\n    ++\t{ .name = CONCERN_STAT_BLOB_LARGEST, .threshold = 26 },\n    ++\t{ .name = CONCERN_STAT_TAG_LARGEST, .threshold =  15 },\n    ++\t{ .name = CONCERN_STAT_COMMIT_INFLATED_SIZE, .threshold = 28 },\n    ++\t{ .name = CONCERN_STAT_TREE_INFLATED_SIZE, .threshold = 32 },\n    ++\t{ .name = CONCERN_STAT_BLOB_INFLATED_SIZE, .threshold = 34 },\n    ++\t{ .name = CONCERN_STAT_TAG_INFLATED_SIZE, .threshold = 25 },\n    ++\t{ 0 }\n     +};\n     +\n    -+/*\n    -+ * Concern thresholds aren't universal. We try to set reasonable default\n    -+ * values, but projects may want to set these thresholds differently. To\n    -+ * support that, they can set the thresholds for their project in the\n    -+ * project's git config, in the section \"repo.structure.concern\".\n    -+  */\n    -+static int config_concern_thresholds(const char *prefixed_var,\n    -+\t\t\t\t     const char *str_val,\n    -+\t\t\t\t     const struct config_context *ctx,\n    -+\t\t\t\t     void *cb UNUSED) {\n    -+\tconst char *config_prefix = \"repo.structure.concern.\";\n    -+\tif (!strncmp(config_prefix, prefixed_var, strlen(config_prefix))) {\n    -+\t\tconst char *concern_name = &prefixed_var[strlen(config_prefix)];\n    -+\t\tfor (int i = 0; concern_stat_thresholds[i].name != 0; i++) {\n    -+\t\t\tif (!strcmp(concern_stat_thresholds[i].name, concern_name)) {\n    -+\t\t\t\tint value = git_config_int(prefixed_var, str_val, ctx->kvi);\n    -+\t\t\t\tif (value < 0)\n    -+\t\t\t\t\treturn -1;\n    -+\t\t\t\tconcern_stat_thresholds[i].threshold = value;\n    -+\t\t\t\treturn 0;\n    -+\t\t\t}\n    ++static size_t get_concern_threshold(const char *name, struct concern_stat_threshold *thresholds) {\n    ++\tfor (size_t i = 0; thresholds[i].name; i++) {\n    ++\t\tif (!strcmp(thresholds[i].name, name)) {\n    ++\t\t\treturn thresholds[i].threshold;\n     +\t\t}\n    -+\t\twarning(_(\"Invalid concern threshold name: '%s'\"), concern_name);\n    -+\t\treturn 0;\n     +\t}\n     +\treturn 0;\n     +}\n     +\n    -+static size_t approx_log2(long value) {\n    -+\tint position = 0;\n    -+\tif (value <= 0) {\n    -+\t\treturn 0;\n    -+\t}\n    -+\twhile (value > 1) {\n    -+\t\tposition++;\n    -+\t\tvalue >>= 1;\n    -+\t}\n    -+\treturn position;\n    -+}\n    -+\n    -+static size_t repo_structure_concern_level(enum concern_stat stat_id,\n    -+\t\t\t\t\t   size_t value) {\n    ++static size_t repo_structure_concern_level(const char* concern_name,\n    ++\t\t\t\t\t   size_t value,\n    ++\t\t\t\t\t   struct concern_stat_threshold *thresholds) {\n    ++\tconst size_t threshold = get_concern_threshold(concern_name, thresholds);\n     +\tsize_t log_value;\n    -+\tif (stat_id >= ARRAY_SIZE(concern_stat_thresholds))\n    -+\t\tBUG(\"unexpected repository stat type\");\n    -+\tlog_value = approx_log2(value);\n    -+\tif (log_value > concern_stat_thresholds[stat_id].threshold)\n    -+\t\treturn log_value -\n    -+\t\t\tconcern_stat_thresholds[stat_id].threshold;\n    ++\tif (threshold == 0)\n    ++\t\tBUG(\"unexpected repository stat threshold: '%s'\", concern_name);\n    ++\tlog_value = log2u(value);\n    ++\tif (log_value > threshold)\n    ++\t\treturn log_value - threshold;\n     +\telse\n     +\t\treturn 0;\n     +}\n    @@ builtin/repo.c: static void stats_table_vaddf(struct stats_table *table,\n      \t\tint unit_width = utf8_strwidth(entry->unit);\n      \t\tif (unit_width > table->unit_col_width)\n     @@ builtin/repo.c: static void stats_table_addf(struct stats_table *table, const char *format, ...)\n    + \tva_end(ap);\n      }\n      \n    ++// define a symbol for 'no concern value for this stat'; this will\n    ++// make the code below easier to read.\n    ++#define CONCERN_STAT_VALUE_NONE 0\n    ++\n      static void stats_table_count_addf(struct stats_table *table, size_t value,\n     -\t\t\t\t   const char *format, ...)\n     +\t\t\t\t   size_t concern, const char *format, ...)\n    @@ builtin/repo.c: static void stats_table_setup_structure(struct stats_table *tabl\n     -\tstats_table_count_addf(table, refs->tags, \"    * %s\", _(\"Tags\"));\n     -\tstats_table_count_addf(table, refs->remotes, \"    * %s\", _(\"Remotes\"));\n     -\tstats_table_count_addf(table, refs->others, \"    * %s\", _(\"Others\"));\n    -+\tstats_table_count_addf(table, ref_total, 0,\n    +-\n    ++\tstats_table_count_addf(table, ref_total, CONCERN_STAT_VALUE_NONE,\n     +\t\t\t       \"  * %s\", _(\"Count\"));\n    -+\tstats_table_count_addf(table, refs->branches, 0,\n    ++\tstats_table_count_addf(table, refs->branches, CONCERN_STAT_VALUE_NONE,\n     +\t\t\t       \"    * %s\", _(\"Branches\"));\n     +\tstats_table_count_addf(table, refs->tags,\n     +\t\t\t       repo_structure_concern_level(\n     +\t\t\t\t       CONCERN_STAT_TAG_COUNT,\n    -+\t\t\t\t       refs->tags),\n    ++\t\t\t\t       refs->tags,\n    ++    \t\t\t               repo_structure_concern_thresholds),\n     +\t\t\t       \"    * %s\", _(\"Tags\"));\n    -+\tstats_table_count_addf(table, refs->remotes, 0,\n    ++\tstats_table_count_addf(table, refs->remotes, CONCERN_STAT_VALUE_NONE,\n     +\t\t\t       \"    * %s\", _(\"Remotes\"));\n    -+\tstats_table_count_addf(table, refs->others, 0,\n    ++\tstats_table_count_addf(table, refs->others, CONCERN_STAT_VALUE_NONE,\n     +\t\t\t       \"    * %s\", _(\"Others\"));\n    - \n      \tobject_count_total = get_total_object_values(&objects->type_counts);\n      \tstats_table_addf(table, \"\");\n      \tstats_table_addf(table, \"* %s\", _(\"Reachable objects\"));\n     -\tstats_table_count_addf(table, object_count_total, \"  * %s\", _(\"Count\"));\n    -+\tstats_table_count_addf(table, object_count_total, 0,\n    ++\tstats_table_count_addf(table, object_count_total, CONCERN_STAT_VALUE_NONE,\n     +\t\t\t       \"  * %s\", _(\"Count\"));\n      \tstats_table_count_addf(table, objects->type_counts.commits,\n     +\t\t\t       repo_structure_concern_level(\n     +\t\t\t\t       CONCERN_STAT_COMMIT_COUNT,\n    -+\t\t\t\t       objects->type_counts.commits),\n    ++\t\t\t\t       objects->type_counts.commits,\n    ++\t\t\t\t       repo_structure_concern_thresholds),\n      \t\t\t       \"    * %s\", _(\"Commits\"));\n      \tstats_table_count_addf(table, objects->type_counts.trees,\n     +\t\t\t       repo_structure_concern_level(\n     +\t\t\t\t       CONCERN_STAT_TREE_COUNT,\n    -+\t\t\t\t       objects->type_counts.trees),\n    ++\t\t\t\t       objects->type_counts.trees,\n    ++\t\t\t\t       repo_structure_concern_thresholds),\n      \t\t\t       \"    * %s\", _(\"Trees\"));\n      \tstats_table_count_addf(table, objects->type_counts.blobs,\n     +\t\t\t       repo_structure_concern_level(\n     +\t\t\t\t       CONCERN_STAT_BLOB_COUNT,\n    -+\t\t\t\t       objects->type_counts.blobs),\n    ++\t\t\t\t       objects->type_counts.blobs,\n    ++\t\t\t\t       repo_structure_concern_thresholds),\n      \t\t\t       \"    * %s\", _(\"Blobs\"));\n      \tstats_table_count_addf(table, objects->type_counts.tags,\n     +\t\t\t       repo_structure_concern_level(\n     +\t\t\t\t       CONCERN_STAT_TAG_COUNT,\n    -+\t\t\t\t       objects->type_counts.tags),\n    ++\t\t\t\t       objects->type_counts.tags,\n    ++\t\t\t\t       repo_structure_concern_thresholds),\n      \t\t\t       \"    * %s\", _(\"Tags\"));\n      \n      \tinflated_object_total = get_total_object_values(&objects->inflated_sizes);\n     -\tstats_table_size_addf(table, inflated_object_total,\n    -+\tstats_table_size_addf(table, inflated_object_total, 0,\n    ++\tstats_table_size_addf(table, inflated_object_total, CONCERN_STAT_VALUE_NONE,\n      \t\t\t      \"  * %s\", _(\"Inflated size\"));\n      \tstats_table_size_addf(table, objects->inflated_sizes.commits,\n     +\t\t\t      repo_structure_concern_level(\n     +\t\t\t\t      CONCERN_STAT_COMMIT_INFLATED_SIZE,\n    -+\t\t\t\t      objects->inflated_sizes.commits),\n    ++\t\t\t\t      objects->inflated_sizes.commits,\n    ++\t\t\t\t      repo_structure_concern_thresholds),\n      \t\t\t      \"    * %s\", _(\"Commits\"));\n      \tstats_table_size_addf(table, objects->inflated_sizes.trees,\n     +\t\t\t      repo_structure_concern_level(\n     +\t\t\t\t      CONCERN_STAT_TREE_INFLATED_SIZE,\n    -+\t\t\t\t      objects->inflated_sizes.trees),\n    ++\t\t\t\t      objects->inflated_sizes.trees,\n    ++\t\t\t\t      repo_structure_concern_thresholds),\n      \t\t\t      \"    * %s\", _(\"Trees\"));\n      \tstats_table_size_addf(table, objects->inflated_sizes.blobs,\n     +\t\t\t      repo_structure_concern_level(\n     +\t\t\t\t      CONCERN_STAT_BLOB_INFLATED_SIZE,\n    -+\t\t\t\t      objects->inflated_sizes.blobs),\n    ++\t\t\t\t      objects->inflated_sizes.blobs,\n    ++\t\t\t\t      repo_structure_concern_thresholds),\n      \t\t\t      \"    * %s\", _(\"Blobs\"));\n      \tstats_table_size_addf(table, objects->inflated_sizes.tags,\n     +\t\t\t      repo_structure_concern_level(\n     +\t\t\t\t      CONCERN_STAT_TAG_INFLATED_SIZE,\n    -+\t\t\t\t      objects->inflated_sizes.tags),\n    ++\t\t\t\t      objects->inflated_sizes.tags,\n    ++\t\t\t\t      repo_structure_concern_thresholds),\n      \t\t\t      \"    * %s\", _(\"Tags\"));\n      \n      \tdisk_object_total = get_total_object_values(&objects->disk_sizes);\n     -\tstats_table_size_addf(table, disk_object_total,\n    -+\tstats_table_size_addf(table, disk_object_total, 0,\n    ++\tstats_table_size_addf(table, disk_object_total, CONCERN_STAT_VALUE_NONE,\n      \t\t\t      \"  * %s\", _(\"Disk size\"));\n      \tstats_table_size_addf(table, objects->disk_sizes.commits,\n     +\t\t\t      repo_structure_concern_level(\n     +\t\t\t\t      CONCERN_STAT_COMMIT_SIZE,\n    -+\t\t\t\t      objects->disk_sizes.commits),\n    ++\t\t\t\t      objects->disk_sizes.commits,\n    ++\t\t\t\t      repo_structure_concern_thresholds),\n      \t\t\t      \"    * %s\", _(\"Commits\"));\n      \tstats_table_size_addf(table, objects->disk_sizes.trees,\n     +\t\t\t      repo_structure_concern_level(\n     +\t\t\t\t      CONCERN_STAT_TREE_SIZE,\n    -+\t\t\t\t      objects->disk_sizes.trees),\n    ++\t\t\t\t      objects->disk_sizes.trees,\n    ++\t\t\t\t      repo_structure_concern_thresholds),\n      \t\t\t      \"    * %s\", _(\"Trees\"));\n      \tstats_table_size_addf(table, objects->disk_sizes.blobs,\n     +\t\t\t      repo_structure_concern_level(\n     +\t\t\t\t      CONCERN_STAT_BLOB_SIZE,\n    -+\t\t\t\t      objects->disk_sizes.blobs),\n    ++\t\t\t\t      objects->disk_sizes.blobs,\n    ++\t\t\t\t      repo_structure_concern_thresholds),\n      \t\t\t      \"    * %s\", _(\"Blobs\"));\n      \tstats_table_size_addf(table, objects->disk_sizes.tags,\n     +\t\t\t      repo_structure_concern_level(\n     +\t\t\t\t      CONCERN_STAT_TAG_SIZE,\n    -+\t\t\t\t      objects->disk_sizes.tags),\n    ++\t\t\t\t      objects->disk_sizes.tags,\n    ++\t\t\t\t      repo_structure_concern_thresholds),\n      \t\t\t      \"    * %s\", _(\"Tags\"));\n      \n      \tstats_table_addf(table, \"\");\n    @@ builtin/repo.c: static void stats_table_setup_structure(struct stats_table *tabl\n      \t\t\t\t     objects->largest.commit_size.value,\n     +\t\t\t\t     repo_structure_concern_level(\n     +\t\t\t\t\t     CONCERN_STAT_COMMIT_LARGEST,\n    -+\t\t\t\t\t     objects->largest.commit_size.value),\n    ++\t\t\t\t\t     objects->largest.commit_size.value,\n    ++\t\t\t\t\t     repo_structure_concern_thresholds),\n      \t\t\t\t     \"    * %s\", _(\"Maximum size\"));\n      \tstats_table_object_count_addf(table,\n      \t\t\t\t      &objects->largest.parent_count.oid,\n      \t\t\t\t      objects->largest.parent_count.value,\n    -+\t\t\t\t      0,\n    ++\t\t\t\t      CONCERN_STAT_VALUE_NONE,\n      \t\t\t\t      \"    * %s\", _(\"Maximum parents\"));\n      \tstats_table_addf(table, \"  * %s\", _(\"Trees\"));\n      \tstats_table_object_size_addf(table,\n      \t\t\t\t     &objects->largest.tree_size.oid,\n      \t\t\t\t     objects->largest.tree_size.value,\n    -+\t\t\t\t      repo_structure_concern_level(\n    -+\t\t\t\t\t      CONCERN_STAT_TREE_LARGEST,\n    -+\t\t\t\t\t      objects->largest.tree_size.value),\n    ++\t\t\t\t     repo_structure_concern_level(\n    ++\t\t\t\t\t     CONCERN_STAT_TREE_LARGEST,\n    ++\t\t\t\t\t     objects->largest.commit_size.value,\n    ++\t\t\t\t\t     repo_structure_concern_thresholds),\n      \t\t\t\t     \"    * %s\", _(\"Maximum size\"));\n    ++\n      \tstats_table_object_count_addf(table,\n    - \t\t\t\t      &objects->largest.tree_entries.oid,\n    - \t\t\t\t      objects->largest.tree_entries.value,\n    -+\t\t\t\t      0,\n    - \t\t\t\t      \"    * %s\", _(\"Maximum entries\"));\n    +-\t\t\t\t      &objects->largest.tree_entries.oid,\n    +-\t\t\t\t      objects->largest.tree_entries.value,\n    +-\t\t\t\t      \"    * %s\", _(\"Maximum entries\"));\n    ++\t\t\t\t     &objects->largest.tree_entries.oid,\n    ++\t\t\t\t     objects->largest.tree_entries.value,\n    ++\t\t\t             CONCERN_STAT_VALUE_NONE,\n    ++\t\t\t\t     \"    * %s\", _(\"Maximum entries\"));\n      \tstats_table_addf(table, \"  * %s\", _(\"Blobs\"));\n      \tstats_table_object_size_addf(table,\n      \t\t\t\t     &objects->largest.blob_size.oid,\n      \t\t\t\t     objects->largest.blob_size.value,\n     +\t\t\t\t     repo_structure_concern_level(\n     +\t\t\t\t\t     CONCERN_STAT_BLOB_LARGEST,\n    -+\t\t\t\t\t     objects->largest.blob_size.value),\n    ++\t\t\t\t\t     objects->largest.blob_size.value,\n    ++\t\t\t\t\t     repo_structure_concern_thresholds),\n      \t\t\t\t     \"    * %s\", _(\"Maximum size\"));\n      \tstats_table_addf(table, \"  * %s\", _(\"Tags\"));\n      \tstats_table_object_size_addf(table,\n    @@ builtin/repo.c: static void stats_table_setup_structure(struct stats_table *tabl\n      \t\t\t\t     objects->largest.tag_size.value,\n     +\t\t\t\t     repo_structure_concern_level(\n     +\t\t\t\t\t     CONCERN_STAT_TAG_LARGEST,\n    -+\t\t\t\t\t     objects->largest.tag_size.value),\n    ++\t\t\t\t\t     objects->largest.tag_size.value,\n    ++\t\t\t\t\t     repo_structure_concern_thresholds),\n      \t\t\t\t     \"    * %s\", _(\"Maximum size\"));\n      }\n      \n    @@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stat\n      \t\t       stats->objects.type_counts.commits, value_delim);\n     +\tif (show_concern)\n     +\t\tprint_keyvalue(\"objects.commits.count.concern_level\", key_delim,\n    -+\t\t\t      repo_structure_concern_level(CONCERN_STAT_COMMIT_COUNT,\n    -+\t\t\t\t\t\t\t   stats->objects.type_counts.commits),\n    -+\t\t\t      value_delim);\n    ++\t\t\t       repo_structure_concern_level(\n    ++\t\t\t\t       CONCERN_STAT_COMMIT_COUNT,\n    ++\t\t\t\t       stats->objects.type_counts.commits,\n    ++\t\t\t\t       repo_structure_concern_thresholds),\n    ++\t\t\t       value_delim);\n      \tprint_keyvalue(\"objects.trees.count\", key_delim,\n      \t\t       stats->objects.type_counts.trees, value_delim);\n     +\tif (show_concern)\n     +\t\tprint_keyvalue(\"objects.trees.count.concern_level\", key_delim,\n    -+\t\t\t      repo_structure_concern_level(CONCERN_STAT_TREE_COUNT,\n    -+\t\t\t\t\t\t\t   stats->objects.type_counts.trees),\n    -+\t\t\t      value_delim);\n    ++\t\t\t       repo_structure_concern_level(\n    ++\t\t\t\t       CONCERN_STAT_TREE_COUNT,\n    ++\t\t\t\t       stats->objects.type_counts.trees,\n    ++\t\t\t\t       repo_structure_concern_thresholds),\n    ++\t\t\t       value_delim);\n      \tprint_keyvalue(\"objects.blobs.count\", key_delim,\n      \t\t       stats->objects.type_counts.blobs, value_delim);\n     +\tif (show_concern)\n     +\t\tprint_keyvalue(\"objects.blobs.count.concern_level\", key_delim,\n    -+\t\t\t      repo_structure_concern_level(CONCERN_STAT_BLOB_COUNT,\n    -+\t\t\t\t\t\t\t   stats->objects.type_counts.blobs),\n    -+\t\t\t      value_delim);\n    ++\t\t\t       repo_structure_concern_level(\n    ++\t\t\t\t       CONCERN_STAT_BLOB_COUNT,\n    ++\t\t\t\t       stats->objects.type_counts.blobs,\n    ++\t\t\t\t       repo_structure_concern_thresholds),\n    ++\t\t value_delim);\n      \tprint_keyvalue(\"objects.tags.count\", key_delim,\n    - \t\t       stats->objects.type_counts.tags, value_delim);\n    +-\t\t       stats->objects.type_counts.tags, value_delim);\n    ++\t\tstats->objects.type_counts.tags, value_delim);\n     +\tif (show_concern)\n     +\t\tprint_keyvalue(\"objects.tags.count.concern_level\", key_delim,\n    -+\t\t\t      repo_structure_concern_level(CONCERN_STAT_TAG_COUNT,\n    -+\t\t\t\t\t\t\t   stats->objects.type_counts.tags),\n    -+\t\t\t      value_delim);\n    ++\t\t\t       repo_structure_concern_level(\n    ++\t\t\t\t       CONCERN_STAT_TAG_COUNT,\n    ++\t\t\t\t       stats->objects.type_counts.tags,\n    ++\t\t\t\t       repo_structure_concern_thresholds),\n    ++\t\t \t       value_delim);\n      \n      \tprint_keyvalue(\"objects.commits.inflated_size\", key_delim,\n    - \t\t       stats->objects.inflated_sizes.commits, value_delim);\n    +-\t\t       stats->objects.inflated_sizes.commits, value_delim);\n    ++\t\tstats->objects.inflated_sizes.commits, value_delim);\n     +\tif (show_concern)\n     +\t\tprint_keyvalue(\"objects.commits.inflated_size.concern_level\", key_delim,\n    -+\t\t\t       repo_structure_concern_level(CONCERN_STAT_COMMIT_INFLATED_SIZE,\n    -+\t\t\t\t\t\t\t    stats->objects.inflated_sizes.commits),\n    -+\t\t\t      value_delim);\n    ++\t\t\t       repo_structure_concern_level(\n    ++\t\t\t\t       CONCERN_STAT_COMMIT_INFLATED_SIZE,\n    ++\t\t\t\t       stats->objects.inflated_sizes.commits,\n    ++\t\t\t\t       repo_structure_concern_thresholds),\n    ++\t\t\t       value_delim);\n      \tprint_keyvalue(\"objects.trees.inflated_size\", key_delim,\n    - \t\t       stats->objects.inflated_sizes.trees, value_delim);\n    +-\t\t       stats->objects.inflated_sizes.trees, value_delim);\n    ++\t\tstats->objects.inflated_sizes.trees, value_delim);\n     +\tif (show_concern)\n     +\t\tprint_keyvalue(\"objects.trees.inflated_size.concern_level\", key_delim,\n    -+\t\t\t       repo_structure_concern_level(CONCERN_STAT_TREE_INFLATED_SIZE,\n    -+\t\t\t\t\t\t\t    stats->objects.inflated_sizes.trees),\n    -+\t\t\t      value_delim);\n    ++\t\t\t       repo_structure_concern_level(\n    ++\t\t\t\t       CONCERN_STAT_TREE_INFLATED_SIZE,\n    ++\t\t\t\t       stats->objects.inflated_sizes.trees,\n    ++\t\t\t\t       repo_structure_concern_thresholds),\n    ++\t\t\t       value_delim);\n     +\n      \tprint_keyvalue(\"objects.blobs.inflated_size\", key_delim,\n      \t\t       stats->objects.inflated_sizes.blobs, value_delim);\n     +\tif (show_concern)\n     +\t\tprint_keyvalue(\"objects.blobs.inflated_size.concern_level\", key_delim,\n    -+\t\t\t       repo_structure_concern_level(CONCERN_STAT_BLOB_INFLATED_SIZE,\n    -+\t\t\t\t\t\t\t    stats->objects.inflated_sizes.blobs),\n    -+\t\t\t      value_delim);\n    ++\t\t\t       repo_structure_concern_level(\n    ++\t\t\t\t       CONCERN_STAT_BLOB_INFLATED_SIZE,\n    ++\t\t\t\t       stats->objects.inflated_sizes.blobs,\n    ++\t\t\t\t       repo_structure_concern_thresholds),\n    ++\t\t\t       value_delim);\n      \tprint_keyvalue(\"objects.tags.inflated_size\", key_delim,\n      \t\t       stats->objects.inflated_sizes.tags, value_delim);\n     +\tif (show_concern)\n     +\t\tprint_keyvalue(\"objects.tags.inflated_size.concern_level\", key_delim,\n    -+\t\t\t       repo_structure_concern_level(CONCERN_STAT_TAG_INFLATED_SIZE,\n    -+\t\t\t\t\t\t\t    stats->objects.inflated_sizes.tags),\n    -+\t\t\t      value_delim);\n    ++\t\t\t       repo_structure_concern_level(\n    ++\t\t\t\t       CONCERN_STAT_TAG_INFLATED_SIZE,\n    ++\t\t\t\t       stats->objects.inflated_sizes.tags,\n    ++\t\t\t\t       repo_structure_concern_thresholds),\n    ++\t\t\t       value_delim);\n      \n      \tprint_keyvalue(\"objects.commits.disk_size\", key_delim,\n      \t\t       stats->objects.disk_sizes.commits, value_delim);\n     +\tif (show_concern)\n     +\t\tprint_keyvalue(\"objects.commits.disk_size.concern_level\", key_delim,\n    -+\t\t\t       repo_structure_concern_level(CONCERN_STAT_COMMIT_SIZE,\n    -+\t\t\t\t\t\t\t    stats->objects.disk_sizes.commits),\n    -+\t\t\t      value_delim);\n    ++\t\t\t       repo_structure_concern_level(\n    ++\t\t\t\t       CONCERN_STAT_COMMIT_SIZE,\n    ++\t\t\t\t       stats->objects.disk_sizes.commits,\n    ++\t\t\t\t       repo_structure_concern_thresholds),\n    ++\t\t\t       value_delim);\n      \tprint_keyvalue(\"objects.trees.disk_size\", key_delim,\n      \t\t       stats->objects.disk_sizes.trees, value_delim);\n     +\tif (show_concern)\n     +\t\tprint_keyvalue(\"objects.trees.disk_size.concern_level\", key_delim,\n    -+\t\t\t       repo_structure_concern_level(CONCERN_STAT_TREE_SIZE,\n    -+\t\t\t\t\t\t\t    stats->objects.disk_sizes.trees),\n    -+\t\t\t      value_delim);\n    ++\t\t\t       repo_structure_concern_level(\n    ++\t\t\t\t       CONCERN_STAT_TREE_SIZE,\n    ++\t\t\t\t       stats->objects.disk_sizes.trees,\n    ++\t\t\t\t       repo_structure_concern_thresholds),\n    ++\t\t\t       value_delim);\n      \tprint_keyvalue(\"objects.blobs.disk_size\", key_delim,\n      \t\t       stats->objects.disk_sizes.blobs, value_delim);\n     +\tif (show_concern)\n    -+\t\tprint_keyvalue(\"objects.blobs.blob_size.concern_level\", key_delim,\n    -+\t\t\t       repo_structure_concern_level(CONCERN_STAT_BLOB_SIZE,\n    -+\t\t\t\t\t\t\t    stats->objects.disk_sizes.blobs),\n    ++\n    ++\t\tprint_keyvalue(\"objects.blobs.disk_size.concern_level\", key_delim,\n    ++\t\t\t       repo_structure_concern_level(\n    ++\t\t\t\t       CONCERN_STAT_BLOB_SIZE,\n    ++\t\t\t\t       stats->objects.disk_sizes.blobs,\n    ++\t\t\t\t       repo_structure_concern_thresholds),\n     +\t\t\t      value_delim);\n      \tprint_keyvalue(\"objects.tags.disk_size\", key_delim,\n      \t\t       stats->objects.disk_sizes.tags, value_delim);\n     +\tif (show_concern)\n     +\t\tprint_keyvalue(\"objects.tags.disk_size.concern_level\", key_delim,\n    -+\t\t\t       repo_structure_concern_level(CONCERN_STAT_TAG_SIZE,\n    -+\t\t\t\t\t\t\t    stats->objects.disk_sizes.tags),\n    ++\t\t\t       repo_structure_concern_level(\n    ++\t\t\t\t       CONCERN_STAT_TAG_SIZE,\n    ++\t\t\t\t       stats->objects.disk_sizes.tags,\n    ++\t\t\t\t       repo_structure_concern_thresholds),\n     +\t\t\t      value_delim);\n      \n      \tprint_object_data(\"objects.commits.max_size\", key_delim,\n      \t\t\t  &stats->objects.largest.commit_size, value_delim);\n     +\tif (show_concern)\n     +\t\tprint_keyvalue(\"objects.commits.max_size.concern_level\", key_delim,\n    -+\t\t\t       repo_structure_concern_level(CONCERN_STAT_COMMIT_LARGEST,\n    -+\t\t\t\t\t\t\t    stats->objects.largest.commit_size.value),\n    -+\t\t\t      value_delim);\n    ++\t\t\t       repo_structure_concern_level(\n    ++\t\t\t\t       CONCERN_STAT_COMMIT_LARGEST,\n    ++\t\t\t\t       stats->objects.largest.commit_size.value,\n    ++\t\t\t\t       repo_structure_concern_thresholds),\n    ++\t\t\t       value_delim);\n      \tprint_object_data(\"objects.trees.max_size\", key_delim,\n      \t\t\t  &stats->objects.largest.tree_size, value_delim);\n     +\tif (show_concern)\n     +\t\tprint_keyvalue(\"objects.trees.max_size.concern_level\", key_delim,\n    -+\t\t\t       repo_structure_concern_level(CONCERN_STAT_TREE_LARGEST,\n    -+\t\t\t\t\t\t\t    stats->objects.largest.tree_size.value),\n    -+\t\t\t      value_delim);\n    ++\t\t\t       repo_structure_concern_level(\n    ++\t\t\t\t       CONCERN_STAT_TREE_LARGEST,\n    ++\t\t\t\t       stats->objects.largest.tree_size.value,\n    ++\t\t\t\t       repo_structure_concern_thresholds),\n    ++\t\t\t       value_delim);\n      \tprint_object_data(\"objects.blobs.max_size\", key_delim,\n      \t\t\t  &stats->objects.largest.blob_size, value_delim);\n     +\tif (show_concern)\n     +\t\tprint_keyvalue(\"objects.blobs.max_size.concern_level\", key_delim,\n    -+\t\t\t       repo_structure_concern_level(CONCERN_STAT_BLOB_LARGEST,\n    -+\t\t\t\t\t\t\t    stats->objects.largest.blob_size.value),\n    -+\t\t\t      value_delim);\n    ++\t\t\t       repo_structure_concern_level(\n    ++\t\t\t\t       CONCERN_STAT_BLOB_LARGEST,\n    ++\t\t\t\t       stats->objects.largest.blob_size.value,\n    ++\t\t\t\t       repo_structure_concern_thresholds),\n    ++\t\t\t       value_delim);\n      \tprint_object_data(\"objects.tags.max_size\", key_delim,\n    - \t\t\t  &stats->objects.largest.tag_size, value_delim);\n    +-\t\t\t  &stats->objects.largest.tag_size, value_delim);\n    ++\t\t          &stats->objects.largest.tag_size, value_delim);\n     +\tif (show_concern)\n     +\t\tprint_keyvalue(\"objects.tags.max_size.concern_level\", key_delim,\n    -+\t\t\t       repo_structure_concern_level(CONCERN_STAT_TAG_LARGEST,\n    -+\t\t\t\t\t\t\t    stats->objects.largest.tag_size.value),\n    -+\t\t\t      value_delim);\n    ++\t\t\t       repo_structure_concern_level(\n    ++\t\t\t\t       CONCERN_STAT_TAG_LARGEST,\n    ++\t\t\t\t       stats->objects.largest.tag_size.value,\n    ++\t\t\t\t       repo_structure_concern_thresholds),\n    ++\t\t\t       value_delim);\n      \n      \tprint_object_data(\"objects.commits.max_parents\", key_delim,\n      \t\t\t  &stats->objects.largest.parent_count, value_delim);\n    @@ builtin/repo.c: static int cmd_repo_structure(int argc, const char **argv, const\n      \t\tOPT_END()\n      \t};\n      \n    + \targc = parse_options(argc, argv, prefix, options, repo_structure_usage, 0);\n    + \tif (argc)\n    + \t\tusage(_(\"too many arguments\"));\n    +-\n    + \trepo_init_revisions(repo, &revs, prefix);\n    + \n    + \tif (show_progress < 0)\n     @@ builtin/repo.c: static int cmd_repo_structure(int argc, const char **argv, const char *prefix,\n      \tswitch (format) {\n      \tcase FORMAT_TABLE:\n    @@ builtin/repo.c: static int cmd_repo_structure(int argc, const char **argv, const\n      \tdefault:\n      \t\tBUG(\"invalid output format\");\n     \n    - ## sideband.c ##\n    + ## repo.c (new) ##\n     @@\n    --#define USE_THE_REPOSITORY_VARIABLE\n    -+define USE_THE_REPOSITORY_VARIABLE\n    - #define DISABLE_SIGN_COMPARE_WARNINGS\n    - \n    - #include \"git-compat-util.h\"\n    ++#include \"repo.h\"\n     \n      ## t/t1901-repo-structure.sh ##\n     @@ t/t1901-repo-structure.sh: test_expect_success 'empty repository' '\n      \t\tEOF\n      \n      \t\tgit repo structure >out 2>err &&\n    --\n    --\t\ttest_cmp expect out &&\n    --\t\ttest_line_count = 0 err\n    --\t)\n    --'\n    --\n    --test_expect_success SHA1 'repository with references and objects' '\n    --\ttest_when_finished \"rm -rf repo\" &&\n    --\tgit init repo &&\n    --\t(\n    --\t\tcd repo &&\n    --\t\ttest_commit_bulk 1005 &&\n    --\t\tgit tag -a foo -m bar &&\n    --\n    --\t\toid=\"$(git rev-parse HEAD)\" &&\n    --\t\tgit update-ref refs/remotes/origin/foo \"$oid\" &&\n    --\n    --\t\t# Also creates a commit, tree, and blob.\n    --\t\tgit notes add -m foo &&\n    --\n    --\t\t# The tags disk size is handled specially due to the\n    --\t\t# git-rev-list(1) --disk-usage=human option printing the full\n    --\t\t# \"byte/bytes\" unit string instead of just \"B\".\n    --\t\tcat >expect <<-EOF &&\n    --\t\t| Repository structure      | Value      |\n    --\t\t| ------------------------- | ---------- |\n    --\t\t| * References              |            |\n    --\t\t|   * Count                 |      4     |\n    --\t\t|     * Branches            |      1     |\n    --\t\t|     * Tags                |      1     |\n    --\t\t|     * Remotes             |      1     |\n    --\t\t|     * Others              |      1     |\n    --\t\t|                           |            |\n    --\t\t| * Reachable objects       |            |\n    --\t\t|   * Count                 |   3.02 k   |\n    --\t\t|     * Commits             |   1.01 k   |\n    --\t\t|     * Trees               |   1.01 k   |\n    --\t\t|     * Blobs               |   1.01 k   |\n    --\t\t|     * Tags                |      1     |\n    --\t\t|   * Inflated size         |  16.03 MiB |\n    --\t\t|     * Commits             | 217.92 KiB |\n    --\t\t|     * Trees               |  15.81 MiB |\n    --\t\t|     * Blobs               |  11.68 KiB |\n    --\t\t|     * Tags                |    132 B   |\n    --\t\t|   * Disk size             | $(object_type_disk_usage all true) |\n    --\t\t|     * Commits             | $(object_type_disk_usage commit true) |\n    --\t\t|     * Trees               | $(object_type_disk_usage tree true) |\n    --\t\t|     * Blobs               |  $(object_type_disk_usage blob true) |\n    --\t\t|     * Tags                |    $(object_type_disk_usage tag) B   |\n    --\t\t|                           |            |\n    --\t\t| * Largest objects         |            |\n    --\t\t|   * Commits               |            |\n    --\t\t|     * Maximum size    [1] |    223 B   |\n    --\t\t|     * Maximum parents [2] |      1     |\n    --\t\t|   * Trees                 |            |\n    --\t\t|     * Maximum size    [3] |  32.29 KiB |\n    --\t\t|     * Maximum entries [4] |   1.01 k   |\n    --\t\t|   * Blobs                 |            |\n    --\t\t|     * Maximum size    [5] |     13 B   |\n    --\t\t|   * Tags                  |            |\n    --\t\t|     * Maximum size    [6] |    132 B   |\n    --\n    --\t\t[1] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a\n    --\t\t[2] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a\n    --\t\t[3] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c\n    --\t\t[4] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c\n    --\t\t[5] 97d808e45116bf02103490294d3d46dad7a2ac62\n    --\t\t[6] 4dae4f5954f5e6feb3577cfb1b181daa3fd3afd2\n    --\t\tEOF\n    --\n    --\t\tgit repo structure >out 2>err &&\n     +\t\tcp out /tmp/test1-out &&\n      \n      \t\ttest_cmp expect out &&\n      \t\ttest_line_count = 0 err\n    -@@ t/t1901-repo-structure.sh: test_expect_success SHA1 'lines and nul format' '\n    - \t)\n    - '\n    - \n    -+test_expect_success SHA1 'lines format with concerns' '\n    -+\ttest_when_finished \"rm -rf repo\" &&\n    -+\tgit init repo &&\n    -+\t(\n    -+\t\tcd repo &&\n    -+\t\t# configure thresholds to get concern indications\n    -+\t\tgit config repo.structure.concern.commit-count 7 &&\n    -+\t\tgit config repo.structure.concern.commit-size 14 &&\n    -+\t\tgit config repo.structure.concern.tree-size 10 &&\n    -+\t\tgit config repo.structure.concern.blob-size 8 &&\n    -+\t\tgit config repo.structure.concern.blob-inflated-size 10 &&\n    -+\t\tgit config repo.structure.concern.tag-size 2 &&\n    -+\t\tgit config repo.structure.concern.commit-largest 4 &&\n    -+\t\tgit config repo.structure.concern.blob-largest 2 &&\n    -+\t\ttouch configured &&\n    -+\t\ttest_commit_bulk 1005 &&\n    -+\t\tgit tag -a foo -m bar &&\n    -+\t\toid=\"$(git rev-parse HEAD)\" &&\n    -+\t\tgit update-ref refs/remotes/origin/foo \"$oid\" &&\n    -+\n    -+\t\t# Also creates a commit, tree, and blob.\n    -+\t\tgit notes add -m foo &&\n    -+\n    -+\n    -+\t\t# The tags disk size is handled specially due to the\n    -+\t\t# git-rev-list(1) --disk-usage=human option printing the full\n    -+\t\t# \"byte/bytes\" unit string instead of just \"B\".\n    -+\t\tcat >expect <<-EOF &&\n    -+\t\tobjects.commits.count.concern_level=2\n    -+\t\tobjects.trees.count.concern_level=0\n    -+\t\tobjects.blobs.count.concern_level=0\n    -+\t\tobjects.tags.count.concern_level=0\n    -+\t\tobjects.commits.inflated_size.concern_level=0\n    -+\t\tobjects.trees.inflated_size.concern_level=0\n    -+\t\tobjects.blobs.inflated_size.concern_level=3\n    -+\t\tobjects.tags.inflated_size.concern_level=0\n    -+\t\tobjects.commits.disk_size.concern_level=3\n    -+\t\tobjects.trees.disk_size.concern_level=8\n    -+\t\tobjects.blobs.blob_size.concern_level=6\n    -+\t\tobjects.tags.disk_size.concern_level=4\n    -+\t\tobjects.commits.max_size.concern_level=3\n    -+\t\tobjects.trees.max_size.concern_level=0\n    -+\t\tobjects.blobs.max_size.concern_level=1\n    -+\t\tobjects.tags.max_size.concern_level=0\n    -+\t\tEOF\n    -+\n    -+\t\tgit repo structure --concern --format=lines >actual 2>err &&\n    -+\t\ttest_grep \"concern_level\" actual >actual-concerns &&\n    -+\t\ttest_cmp expect actual-concerns &&\n    -+\t\ttest_line_count = 0 err\n    -+\t)\n    -+'\n    -+\n    - test_expect_success 'progress meter option' '\n    - \ttest_when_finished \"rm -rf repo\" &&\n    - \tgit init repo &&\n2:  56fb5590ea ! 2:  f0e25fae09 repo: make concern thresholds configurable\n    @@ Commit message\n         configure per-repo concern levels for \"git repo structure\"\n         concern reporting.\n     \n    +    Signed-off-by: Mark C. Chu-Carroll <markchucarroll@fastmail.com>\n    +\n      ## Documentation/config/repo.adoc (new) ##\n     @@\n     +`repo.concern.commit-count`::\n    @@ builtin/repo.c\n      static const char *const repo_usage[] = {\n      \tREPO_INFO_USAGE,\n      \tREPO_STRUCTURE_USAGE,\n    -@@ builtin/repo.c: enum concern_stat {\n    - \tCONCERN_STAT_TREE_INFLATED_SIZE,\n    - \tCONCERN_STAT_BLOB_INFLATED_SIZE,\n    - \tCONCERN_STAT_TAG_INFLATED_SIZE,\n    -+\tCONCERN_STAT_MAXIMUM\n    - };\n    - \n    --struct concern_stat_threshold repo_structure_concern_thresholds[] = {\n    --\t{ .name = \"commit_count\", .threshold = 19 },\n    --\t{.name = \"tree_count\", .threshold = 21 },\n    --\t{ .name = \"blob_count\", .threshold = 22 },\n    --\t{ .name = \"tag_count\", .threshold = 15 },\n    --\t{ .name = \"commit_size\", .threshold = 28 },\n    --\t{ .name = \"tree_size\", .threshold = 27 },\n    --\t{ .name = \"blob_size\", .threshold = 36 },\n    --\t{ .name = \"tags_size\", .threshold = 18 },\n    --\t{ .name = \"commit_largest\", .threshold = 18  },\n    --\t{ .name = \"tree_largest\", .threshold = 18 },\n    --\t{ .name = \"blob_largest\", .threshold = 26 },\n    --\t{ .name = \"tag_largest\", .threshold =  15 },\n    --\t{ .name = \"commit_inflated_size\", .threshold = 28 },\n    --\t{ .name = \"tree_inflated_size\", .threshold = 32 },\n    --\t{ .name = \"blob_inflated_size\", .threshold = 34 },\n    --\t{ .name = \"tag_inflated_size\", .threshold = 25}\n    -+struct concern_stat_threshold concern_stat_thresholds[] = {\n    -+\t{ .name = \"commit-count\", .threshold = 19 },\n    -+\t{ .name = \"tree-count\", .threshold = 21 },\n    -+\t{ .name = \"blob-count\", .threshold = 22 },\n    -+\t{ .name = \"tag-count\", .threshold = 15 },\n    -+\t{ .name = \"commit-size\", .threshold = 28 },\n    -+\t{ .name = \"tree-size\", .threshold = 27 },\n    -+\t{ .name = \"blob-size\", .threshold = 36 },\n    -+\t{ .name = \"tag-size\", .threshold = 18 },\n    -+\t{ .name = \"commit-largest\", .threshold = 18  },\n    -+\t{ .name = \"tree-largest\", .threshold = 18 },\n    -+\t{ .name = \"blob-largest\", .threshold = 26 },\n    -+\t{ .name = \"tag-largest\", .threshold =  15 },\n    -+\t{ .name = \"commit-inflated-size\", .threshold = 28 },\n    -+\t{ .name = \"tree-inflated-size\", .threshold = 32 },\n    -+\t{ .name = \"blob-inflated-size\", .threshold = 34 },\n    -+\t{ .name = \"tag-inflated-size\", .threshold = 25},\n    -+\t{ 0 }\n    - };\n    - \n    - /*\n    -  * Concern thresholds aren't universal. We try to set reasonable default\n    -  * values, but projects may want to set these thresholds differently. To\n    -  * support that, they can set the thresholds for their project in the\n    -- * project's git config, in the section \"repo.structure.concern\".\n    -+ * project's git config, in the section \"repo.concern\".\n    -   */\n    - static int config_concern_thresholds(const char *prefixed_var,\n    - \t\t\t\t     const char *str_val,\n    - \t\t\t\t     const struct config_context *ctx,\n    - \t\t\t\t     void *cb UNUSED) {\n    --\tconst char *config_prefix = \"repo.structure.concern.\";\n    --\tif (!strncmp(config_prefix, prefixed_var, strlen(config_prefix))) {\n    --\t\tconst char *concern_name = &prefixed_var[strlen(config_prefix)];\n    -+\tif (!strncmp(REPO_STRUCTURE_CONFIG_PREFIX, prefixed_var,\n    -+\t      strlen(REPO_STRUCTURE_CONFIG_PREFIX))) {\n    -+\t\tconst char *concern_name =\n    -+\t\t\t&prefixed_var[strlen(REPO_STRUCTURE_CONFIG_PREFIX)];\n    - \t\tfor (int i = 0; concern_stat_thresholds[i].name != 0; i++) {\n    - \t\t\tif (!strcmp(concern_stat_thresholds[i].name, concern_name)) {\n    - \t\t\t\tint value = git_config_int(prefixed_var, str_val, ctx->kvi);\n    -@@ builtin/repo.c: static int config_concern_thresholds(const char *prefixed_var,\n    - \treturn 0;\n    +@@ builtin/repo.c: static size_t repo_structure_concern_level(const char* concern_name,\n    + \t\treturn 0;\n      }\n      \n    --static size_t approx_log2(long value) {\n    -+static size_t approx_log2(size_t value) {\n    - \tint position = 0;\n    - \tif (value <= 0) {\n    - \t\treturn 0;\n    -@@ builtin/repo.c: static size_t approx_log2(long value) {\n    - static size_t repo_structure_concern_level(enum concern_stat stat_id,\n    - \t\t\t\t\t   size_t value) {\n    - \tsize_t log_value;\n    --\tif (stat_id >= ARRAY_SIZE(concern_stat_thresholds))\n    -+\tif (stat_id >= CONCERN_STAT_MAXIMUM)\n    - \t\tBUG(\"unexpected repository stat type\");\n    - \tlog_value = approx_log2(value);\n    - \tif (log_value > concern_stat_thresholds[stat_id].threshold)\n    ++/*\n    ++ * Concern thresholds aren't universal. We try to set reasonable default\n    ++ * values, but projects may want to set these thresholds differently. To\n    ++ * support that, they can set the thresholds for their project in the\n    ++ * project's git config, in the section \"repo.structure.concern\".\n    ++  */\n    ++static int config_concern_thresholds(const char *prefixed_var,\n    ++\t\t\t\t     const char *str_val,\n    ++\t\t\t\t     const struct config_context *ctx,\n    ++\t\t\t\t     void *cb)\n    ++{\n    ++\tstruct concern_stat_threshold *thresholds = cb;\n    ++\tif (starts_with(prefixed_var, REPO_STRUCTURE_CONFIG_PREFIX)) {\n    ++\t\tconst char *concern_name = &prefixed_var[strlen(REPO_STRUCTURE_CONFIG_PREFIX)];\n    ++\t\tfor (int i = 0; thresholds[i].name; i++) {\n    ++\t\t\tif (!strcmp(thresholds[i].name, concern_name)) {\n    ++\t\t\t\tint value = git_config_int(prefixed_var, str_val, ctx->kvi);\n    ++\t\t\t\tif (value < 0)\n    ++\t\t\t\t\treturn -1;\n    ++\t\t\t\tthresholds[i].threshold = value;\n    ++\t\t\t\treturn 0;\n    ++\t\t\t}\n    ++\t\t}\n    ++\t\twarning(_(\"Invalid concern threshold name: '%s'\"), concern_name);\n    ++\t\treturn 0;\n    ++\t}\n    ++\treturn 0;\n    ++}\n    ++\n    + /*\n    +  * Holds column data that gets stored for each row.\n    +  */\n     @@ builtin/repo.c: static void stats_table_print_structure(const struct stats_table *table,\n      \t\t\tstrbuf_utf8_align(&buf, ALIGN_LEFT, concern_col_width, concern_str.buf);\n      \t\t\tstrbuf_addstr(&buf, \" |\");\n    @@ builtin/repo.c: static void stats_table_print_structure(const struct stats_table\n      \t}\n      \n     @@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stats,\n    - \tprint_keyvalue(\"objects.blobs.disk_size\", key_delim,\n    - \t\t       stats->objects.disk_sizes.blobs, value_delim);\n    - \tif (show_concern)\n    --\t\tprint_keyvalue(\"objects.blobs.blob_size.concern_level\", key_delim,\n    -+\t\tprint_keyvalue(\"objects.blobs.disk_size.concern_level\", key_delim,\n    - \t\t\t       repo_structure_concern_level(CONCERN_STAT_BLOB_SIZE,\n    - \t\t\t\t\t\t\t    stats->objects.disk_sizes.blobs),\n    - \t\t\t      value_delim);\n    -@@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stats,\n    - \t\t\t\t\t\t\t    stats->objects.largest.commit_size.value),\n    - \t\t\t      value_delim);\n    - \tprint_object_data(\"objects.trees.max_size\", key_delim,\n    --\t\t\t  &stats->objects.largest.tree_size, value_delim);\n    -+\t\t\t &stats->objects.largest.tree_size, value_delim);\n    - \tif (show_concern)\n    - \t\tprint_keyvalue(\"objects.trees.max_size.concern_level\", key_delim,\n    - \t\t\t       repo_structure_concern_level(CONCERN_STAT_TREE_LARGEST,\n    -@@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stats,\n    - \t\t\t\t\t\t\t    stats->objects.largest.blob_size.value),\n    - \t\t\t      value_delim);\n    - \tprint_object_data(\"objects.tags.max_size\", key_delim,\n    --\t\t\t  &stats->objects.largest.tag_size, value_delim);\n    -+\t\t\t   &stats->objects.largest.tag_size, value_delim);\n    - \tif (show_concern)\n    - \t\tprint_keyvalue(\"objects.tags.max_size.concern_level\", key_delim,\n    - \t\t\t       repo_structure_concern_level(CONCERN_STAT_TAG_LARGEST,\n    + \t\t\t\t       stats->objects.largest.tag_size.value,\n    + \t\t\t\t       repo_structure_concern_thresholds),\n    + \t\t\t       value_delim);\n    +-\n    + \tprint_object_data(\"objects.commits.max_parents\", key_delim,\n    + \t\t\t  &stats->objects.largest.parent_count, value_delim);\n    + \tprint_object_data(\"objects.trees.max_entries\", key_delim,\n     @@ builtin/repo.c: static int cmd_repo_structure(int argc, const char **argv, const char *prefix,\n    - \targc = parse_options(argc, argv, prefix, options, repo_structure_usage, 0);\n    - \tif (argc)\n      \t\tusage(_(\"too many arguments\"));\n    --\n    -+\tif (show_concern)\n    -+\t\trepo_config(repo, &config_concern_thresholds, NULL);\n      \trepo_init_revisions(repo, &revs, prefix);\n      \n    ++\tif (show_concern)\n    ++                repo_config(repo, &config_concern_thresholds, &repo_structure_concern_thresholds);\n    ++\n    ++\n      \tif (show_progress < 0)\n    -\n    - ## sideband.c ##\n    -@@\n    --define USE_THE_REPOSITORY_VARIABLE\n    -+#define USE_THE_REPOSITORY_VARIABLE\n    - #define DISABLE_SIGN_COMPARE_WARNINGS\n    + \t\tshow_progress = isatty(2);\n      \n    - #include \"git-compat-util.h\"\n     \n      ## t/t1901-repo-structure.sh ##\n     @@ t/t1901-repo-structure.sh: test_expect_success 'empty repository' '\n    @@ t/t1901-repo-structure.sh: test_expect_success 'empty repository' '\n      \n      \t\ttest_cmp expect out &&\n      \t\ttest_line_count = 0 err\n    +@@ t/t1901-repo-structure.sh: test_expect_success SHA1 'repository with references and objects' '\n      \t)\n      '\n      \n    -+test_expect_success SHA1 'repository with references and objects' '\n    -+\ttest_when_finished \"rm -rf repo\" &&\n    -+\tgit init repo &&\n    -+\t(\n    -+\t\tcd repo &&\n    -+\t\ttest_commit_bulk 1005 &&\n    -+\t\tgit tag -a foo -m bar &&\n    -+\n    -+\t\toid=\"$(git rev-parse HEAD)\" &&\n    -+\t\tgit update-ref refs/remotes/origin/foo \"$oid\" &&\n    -+\n    -+\t\t# Also creates a commit, tree, and blob.\n    -+\t\tgit notes add -m foo &&\n    -+\n    -+\t\t# The tags disk size is handled specially due to the\n    -+\t\t# git-rev-list(1) --disk-usage=human option printing the full\n    -+\t\t# \"byte/bytes\" unit string instead of just \"B\".\n    -+\t\tcat >expect <<-EOF &&\n    -+\t\t| Repository structure      | Value      |\n    -+\t\t| ------------------------- | ---------- |\n    -+\t\t| * References              |            |\n    -+\t\t|   * Count                 |      4     |\n    -+\t\t|     * Branches            |      1     |\n    -+\t\t|     * Tags                |      1     |\n    -+\t\t|     * Remotes             |      1     |\n    -+\t\t|     * Others              |      1     |\n    -+\t\t|                           |            |\n    -+\t\t| * Reachable objects       |            |\n    -+\t\t|   * Count                 |   3.02 k   |\n    -+\t\t|     * Commits             |   1.01 k   |\n    -+\t\t|     * Trees               |   1.01 k   |\n    -+\t\t|     * Blobs               |   1.01 k   |\n    -+\t\t|     * Tags                |      1     |\n    -+\t\t|   * Inflated size         |  16.03 MiB |\n    -+\t\t|     * Commits             | 217.92 KiB |\n    -+\t\t|     * Trees               |  15.81 MiB |\n    -+\t\t|     * Blobs               |  11.68 KiB |\n    -+\t\t|     * Tags                |    132 B   |\n    -+\t\t|   * Disk size             | $(object_type_disk_usage all true) |\n    -+\t\t|     * Commits             | $(object_type_disk_usage commit true) |\n    -+\t\t|     * Trees               | $(object_type_disk_usage tree true) |\n    -+\t\t|     * Blobs               |  $(object_type_disk_usage blob true) |\n    -+\t\t|     * Tags                |    $(object_type_disk_usage tag) B   |\n    -+\t\t|                           |            |\n    -+\t\t| * Largest objects         |            |\n    -+\t\t|   * Commits               |            |\n    -+\t\t|     * Maximum size    [1] |    223 B   |\n    -+\t\t|     * Maximum parents [2] |      1     |\n    -+\t\t|   * Trees                 |            |\n    -+\t\t|     * Maximum size    [3] |  32.29 KiB |\n    -+\t\t|     * Maximum entries [4] |   1.01 k   |\n    -+\t\t|   * Blobs                 |            |\n    -+\t\t|     * Maximum size    [5] |     13 B   |\n    -+\t\t|   * Tags                  |            |\n    -+\t\t|     * Maximum size    [6] |    132 B   |\n    -+\n    -+\t\t[1] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a\n    -+\t\t[2] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a\n    -+\t\t[3] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c\n    -+\t\t[4] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c\n    -+\t\t[5] 97d808e45116bf02103490294d3d46dad7a2ac62\n    -+\t\t[6] 4dae4f5954f5e6feb3577cfb1b181daa3fd3afd2\n    -+\t\tEOF\n    -+\n    -+\t\tgit repo structure >out 2>err &&\n    -+\n    -+\t\ttest_cmp expect out &&\n    -+\t\ttest_line_count = 0 err\n    -+\t)\n    -+'\n    -+\n     +test_expect_success SHA1 'concern flagging' '\n     +\ttest_when_finished \"rm -rf repo\" &&\n     +\tgit init repo &&\n    @@ t/t1901-repo-structure.sh: test_expect_success 'empty repository' '\n      test_expect_success SHA1 'lines and nul format' '\n      \ttest_when_finished \"rm -rf repo\" &&\n      \tgit init repo &&\n    -@@ t/t1901-repo-structure.sh: test_expect_success SHA1 'lines format with concerns' '\n    - \tgit init repo &&\n    - \t(\n    - \t\tcd repo &&\n    -+\t\t# differences between zlib and zlib-ng can cause this\n    -+\t\t# test to fail; so we just turn it off.\n    +@@ t/t1901-repo-structure.sh: test_expect_success SHA1 'lines and nul format' '\n    + \t)\n    + '\n    + \n    ++test_expect_success SHA1 'lines format with concerns' '\n    ++\ttest_when_finished \"rm -rf repo\" &&\n    ++\tgit init repo &&\n    ++\t(\n    ++\t\tcd repo &&\n    ++\t\t# configure thresholds to get concern indications\n     +\t\tgit config core.compression 0 &&\n    - \t\t# configure thresholds to get concern indications\n    --\t\tgit config repo.structure.concern.commit-count 7 &&\n    --\t\tgit config repo.structure.concern.commit-size 14 &&\n    --\t\tgit config repo.structure.concern.tree-size 10 &&\n    --\t\tgit config repo.structure.concern.blob-size 8 &&\n    --\t\tgit config repo.structure.concern.blob-inflated-size 10 &&\n    --\t\tgit config repo.structure.concern.tag-size 2 &&\n    --\t\tgit config repo.structure.concern.commit-largest 4 &&\n    --\t\tgit config repo.structure.concern.blob-largest 2 &&\n    --\t\ttouch configured &&\n     +\t\tgit config repo.concern.commit-count 7 &&\n     +\t\tgit config repo.concern.commit-size 14 &&\n     +\t\tgit config repo.concern.tree-size 10 &&\n    @@ t/t1901-repo-structure.sh: test_expect_success SHA1 'lines format with concerns'\n     +\t\tgit config repo.concern.tag-size 2 &&\n     +\t\tgit config repo.concern.commit-largest 4 &&\n     +\t\tgit config repo.concern.blob-largest 2 &&\n    - \t\ttest_commit_bulk 1005 &&\n    - \t\tgit tag -a foo -m bar &&\n    - \t\toid=\"$(git rev-parse HEAD)\" &&\n    -@@ t/t1901-repo-structure.sh: test_expect_success SHA1 'lines format with concerns' '\n    - \t\t# Also creates a commit, tree, and blob.\n    - \t\tgit notes add -m foo &&\n    - \n    --\n    - \t\t# The tags disk size is handled specially due to the\n    - \t\t# git-rev-list(1) --disk-usage=human option printing the full\n    - \t\t# \"byte/bytes\" unit string instead of just \"B\".\n    -@@ t/t1901-repo-structure.sh: test_expect_success SHA1 'lines format with concerns' '\n    - \t\tobjects.tags.inflated_size.concern_level=0\n    - \t\tobjects.commits.disk_size.concern_level=3\n    - \t\tobjects.trees.disk_size.concern_level=8\n    --\t\tobjects.blobs.blob_size.concern_level=6\n    --\t\tobjects.tags.disk_size.concern_level=4\n    ++\t\ttouch configured &&\n    ++\t\ttest_commit_bulk 1005 &&\n    ++\t\tgit tag -a foo -m bar &&\n    ++\t\toid=\"$(git rev-parse HEAD)\" &&\n    ++\t\tgit update-ref refs/remotes/origin/foo \"$oid\" &&\n    ++\n    ++\t\t# Also creates a commit, tree, and blob.\n    ++\t\tgit notes add -m foo &&\n    ++\n    ++\n    ++\t\t# The tags disk size is handled specially due to the\n    ++\t\t# git-rev-list(1) --disk-usage=human option printing the full\n    ++\t\t# \"byte/bytes\" unit string instead of just \"B\".\n    ++\t\tcat >expect <<-EOF &&\n    ++\t\tobjects.commits.count.concern_level=2\n    ++\t\tobjects.trees.count.concern_level=0\n    ++\t\tobjects.blobs.count.concern_level=0\n    ++\t\tobjects.tags.count.concern_level=0\n    ++\t\tobjects.commits.inflated_size.concern_level=0\n    ++\t\tobjects.trees.inflated_size.concern_level=0\n    ++\t\tobjects.blobs.inflated_size.concern_level=3\n    ++\t\tobjects.tags.inflated_size.concern_level=0\n    ++\t\tobjects.commits.disk_size.concern_level=3\n    ++\t\tobjects.trees.disk_size.concern_level=8\n     +\t\tobjects.blobs.disk_size.concern_level=6\n     +\t\tobjects.tags.disk_size.concern_level=5\n    - \t\tobjects.commits.max_size.concern_level=3\n    - \t\tobjects.trees.max_size.concern_level=0\n    - \t\tobjects.blobs.max_size.concern_level=1\n    ++\t\tobjects.commits.max_size.concern_level=3\n    ++\t\tobjects.trees.max_size.concern_level=0\n    ++\t\tobjects.blobs.max_size.concern_level=1\n    ++\t\tobjects.tags.max_size.concern_level=0\n    ++\t\tEOF\n    ++\n    ++\t\tgit repo structure --concern --format=lines >actual 2>err &&\n    ++\t\ttest_grep \"concern_level\" actual >actual-concerns &&\n    ++\t\ttest_cmp expect actual-concerns &&\n    ++\t\ttest_line_count = 0 err\n    ++\t)\n    ++'\n    ++\n    + test_expect_success 'progress meter option' '\n    + \ttest_when_finished \"rm -rf repo\" &&\n    + \tgit init repo &&\n\n---\nbase-commit: 3bc0341126508f78f5869cbfc0005e987efdf0c7\nchange-id: 20261019-b4-repo-structure-concern-f1d6f6326095\n\n\n"},{"id":"556807","messageId":"20261029-b4-repo-structure-concern-v4-1-d813537ec617@fastmail.com","threadId":"66621","inReplyTo":"20261029-b4-repo-structure-concern-v4-0-d813537ec617@fastmail.com","subject":"[PATCH v4 1/2] repo: add level of concern reporting","fromName":"Mark C. Chu-Carroll","fromEmail":"markchucarroll@fastmail.com","sentAt":"2026-10-29T17:52:22Z","receivedAt":"2026-10-29T17:52:22Z","isPatch":true,"sender":{"key":"markchucarroll@fastmail.com","avatar":null},"body":"\"git repo structure\" computes a set of statistics\nthat are useful for diagnosing performance issues in\na git repository. But for a typical user who hasn't\nencountered any issues yet, it can be difficult to\ninterpret the data - when should a value be considered\nconcerning?\n\nAdd a new option to \"git repo structure\" which causes it\nto generate an additional column providing a level of concern\nfor many values.\n\nTo select defaults for the thresholds where a value becomes concern,\na (hopefully) representative selection of repositories were analyzed.\nBased on empirical exploration of the data, the measurement that seemed\nclosest to accurately assessing when a repository's structure becomes\nconcerning was based on the base 2 logarithm of the value. Across all of\nthe sample repositories, for all of the measurements, the point at which\nthings become problematic was when a base 2 logarithm of a value was\nmore than 1 standard deviation from the mean of the log_2 values in\nthe sample set.\n\nSigned-off-by: Mark C. Chu-Carroll <markchucarroll@fastmail.com>\n---\n Documentation/git-repo.adoc |  16 +-\n builtin/repo.c              | 370 ++++++++++++++++++++++++++++++++++++++++----\n repo.c                      |   1 +\n t/t1901-repo-structure.sh   |   1 +\n 4 files changed, 359 insertions(+), 29 deletions(-)\n\ndiff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc\nindex ed7d80c690..f186b27d87 100644\n--- a/Documentation/git-repo.adoc\n+++ b/Documentation/git-repo.adoc\n@@ -10,7 +10,7 @@ SYNOPSIS\n [synopsis]\n git repo info [--format=(lines|nul) | -z] [--all | <key>...]\n git repo info --keys [--format=(lines|nul) | -z]\n-git repo structure [--format=(table|lines|nul) | -z]\n+git repo structure [--format=(table|lines|nul) | -z] [--[no-]concern]\n \n DESCRIPTION\n -----------\n@@ -56,7 +56,7 @@ supported:\n `nul`:::\n \tSimilar to `lines`, but using a _NUL_ character after each value.\n \n-`structure [--format=(table|lines|nul) | -z]`::\n+`structure [--format=(table|lines|nul) | -z] [--[no-]concern]`::\n \tRetrieve statistics about the current repository structure. The\n \tfollowing kinds of information are reported:\n +\n@@ -88,6 +88,18 @@ supported:\n \tquoted.\n +\n `-z` is an alias for `--format=nul`.\n++\n+To make it easier to know when one of the values is something that\n+users should be concerned about, adding '--concern' will provide a\n+level of concern to be added to the output. In 'table' mode, this will\n+be a table column indicating the level of concern as a number of stars;\n+in `lines` or `nul` mode, a `concern_level` line will be added for each\n+statistic with a numeric value.\n++\n+The level of concern for each statistic is computed using the log_2 of the\n+statistic value. The value is considered concerning if it exceeds a heuristically\n+chosen threshold value; the degree of concern is the difference between the\n+log_2 of the value, and the log_2 of the corresponding threshold.\n \n INFO KEYS\n ---------\ndiff --git a/builtin/repo.c b/builtin/repo.c\nindex 84e012f83f..bd1866c5fa 100644\n--- a/builtin/repo.c\n+++ b/builtin/repo.c\n@@ -21,13 +21,14 @@\n #include \"tree.h\"\n #include \"tree-walk.h\"\n #include \"utf8.h\"\n+#include \"wrapper.h\"\n \n #define REPO_INFO_USAGE \\\n \t\"git repo info [--format=(lines|nul) | -z] [--all | <key>...]\", \\\n \t\"git repo info --keys [--format=(lines|nul) | -z]\"\n \n #define REPO_STRUCTURE_USAGE \\\n-\t\"git repo structure [--format=(table|lines|nul) | -z]\"\n+\t\"git repo structure [--format=(table|lines|nul) | -z] [--[no-]concern]\"\n \n static const char *const repo_usage[] = {\n \tREPO_INFO_USAGE,\n@@ -348,8 +349,87 @@ struct stats_table {\n \tint name_col_width;\n \tint value_col_width;\n \tint unit_col_width;\n+\tint concern_col_width;\n };\n \n+/*\n+ * For computing level of concern about a stat, we need to have some\n+ * baseline values. For each of these, we'define a minimum level\n+ * of concern, which will appear as 1 star. Then we scale exponentially:\n+ * each time that value doubles, we add another star.\n+ *\n+ * The threshold values for concern are expressed as log_2 values of the size\n+ * at which the statistic becomes concerning. The default values for the\n+ * threshold were selected based on observed values from a collection of\n+ * representative-ish repositories of varying size, by finding the mean log_2\n+ * value and standard deviation (sigma) for each statistic over the collection.\n+ * The threshold for each metric is then set at mean(log_2) + sigma.\n+ */\n+struct concern_stat_threshold {\n+\tconst char *name;\n+\tsize_t threshold;\n+};\n+\n+#define CONCERN_STAT_COMMIT_COUNT \"commit-count\"\n+#define CONCERN_STAT_TREE_COUNT \"tree-count\"\n+#define CONCERN_STAT_BLOB_COUNT \"blob-count\"\n+#define CONCERN_STAT_TAG_COUNT \"tag-count\"\n+#define CONCERN_STAT_COMMIT_SIZE \"commit-size\"\n+#define CONCERN_STAT_TREE_SIZE \"tree-size\"\n+#define CONCERN_STAT_BLOB_SIZE \"blob-size\"\n+#define CONCERN_STAT_TAG_SIZE \"tag-size\"\n+#define CONCERN_STAT_COMMIT_LARGEST \"commit-largest\"\n+#define CONCERN_STAT_TREE_LARGEST \"tree-largest\"\n+#define CONCERN_STAT_BLOB_LARGEST \"blob-largest\"\n+#define CONCERN_STAT_TAG_LARGEST \"tag-largest\"\n+#define CONCERN_STAT_COMMIT_INFLATED_SIZE \"commit-inflated-size\"\n+#define CONCERN_STAT_TREE_INFLATED_SIZE \"tree-inflated-size\"\n+#define CONCERN_STAT_BLOB_INFLATED_SIZE \"blob-inflated-size\"\n+#define CONCERN_STAT_TAG_INFLATED_SIZE \"tag-inflated-size\"\n+\n+struct concern_stat_threshold repo_structure_concern_thresholds[] = {\n+\t{ .name = CONCERN_STAT_COMMIT_COUNT, .threshold = 19 },\n+\t{ .name = CONCERN_STAT_TREE_COUNT, .threshold = 21 },\n+\t{ .name = CONCERN_STAT_BLOB_COUNT, .threshold = 22 },\n+\t{ .name = CONCERN_STAT_TAG_COUNT, .threshold = 15 },\n+\t{ .name = CONCERN_STAT_COMMIT_SIZE, .threshold = 28 },\n+\t{ .name = CONCERN_STAT_TREE_SIZE, .threshold = 27 },\n+\t{ .name = CONCERN_STAT_BLOB_SIZE, .threshold = 36 },\n+\t{ .name = CONCERN_STAT_TAG_SIZE, .threshold = 18 },\n+\t{ .name = CONCERN_STAT_COMMIT_LARGEST, .threshold = 18  },\n+\t{ .name = CONCERN_STAT_TREE_LARGEST, .threshold = 18 },\n+\t{ .name = CONCERN_STAT_BLOB_LARGEST, .threshold = 26 },\n+\t{ .name = CONCERN_STAT_TAG_LARGEST, .threshold =  15 },\n+\t{ .name = CONCERN_STAT_COMMIT_INFLATED_SIZE, .threshold = 28 },\n+\t{ .name = CONCERN_STAT_TREE_INFLATED_SIZE, .threshold = 32 },\n+\t{ .name = CONCERN_STAT_BLOB_INFLATED_SIZE, .threshold = 34 },\n+\t{ .name = CONCERN_STAT_TAG_INFLATED_SIZE, .threshold = 25 },\n+\t{ 0 }\n+};\n+\n+static size_t get_concern_threshold(const char *name, struct concern_stat_threshold *thresholds) {\n+\tfor (size_t i = 0; thresholds[i].name; i++) {\n+\t\tif (!strcmp(thresholds[i].name, name)) {\n+\t\t\treturn thresholds[i].threshold;\n+\t\t}\n+\t}\n+\treturn 0;\n+}\n+\n+static size_t repo_structure_concern_level(const char* concern_name,\n+\t\t\t\t\t   size_t value,\n+\t\t\t\t\t   struct concern_stat_threshold *thresholds) {\n+\tconst size_t threshold = get_concern_threshold(concern_name, thresholds);\n+\tsize_t log_value;\n+\tif (threshold == 0)\n+\t\tBUG(\"unexpected repository stat threshold: '%s'\", concern_name);\n+\tlog_value = log2u(value);\n+\tif (log_value > threshold)\n+\t\treturn log_value - threshold;\n+\telse\n+\t\treturn 0;\n+}\n+\n /*\n  * Holds column data that gets stored for each row.\n  */\n@@ -358,6 +438,7 @@ struct stats_table_entry {\n \tconst char *unit;\n \tsize_t index;\n \tstruct object_id *oid;\n+\tsize_t concern;\n };\n \n static void stats_table_vaddf(struct stats_table *table,\n@@ -391,6 +472,8 @@ static void stats_table_vaddf(struct stats_table *table,\n \t\tif (value_width > table->value_col_width)\n \t\t\ttable->value_col_width = value_width;\n \t}\n+\tif ((int)entry->concern > table->concern_col_width)\n+\t\ttable->concern_col_width = entry->concern;\n \tif (entry->unit) {\n \t\tint unit_width = utf8_strwidth(entry->unit);\n \t\tif (unit_width > table->unit_col_width)\n@@ -409,14 +492,19 @@ static void stats_table_addf(struct stats_table *table, const char *format, ...)\n \tva_end(ap);\n }\n \n+// define a symbol for 'no concern value for this stat'; this will\n+// make the code below easier to read.\n+#define CONCERN_STAT_VALUE_NONE 0\n+\n static void stats_table_count_addf(struct stats_table *table, size_t value,\n-\t\t\t\t   const char *format, ...)\n+\t\t\t\t   size_t concern, const char *format, ...)\n {\n \tstruct stats_table_entry *entry;\n \tva_list ap;\n \n \tCALLOC_ARRAY(entry, 1);\n \thumanise_count(value, &entry->value, &entry->unit);\n+\tentry->concern = concern;\n \n \tva_start(ap, format);\n \tstats_table_vaddf(table, entry, format, ap);\n@@ -425,13 +513,14 @@ static void stats_table_count_addf(struct stats_table *table, size_t value,\n \n static void stats_table_object_count_addf(struct stats_table *table,\n \t\t\t\t\t  struct object_id *oid, size_t value,\n-\t\t\t\t\t  const char *format, ...)\n+\t\t\t\t\t  size_t concern, const char *format, ...)\n {\n \tstruct stats_table_entry *entry;\n \tva_list ap;\n \n \tCALLOC_ARRAY(entry, 1);\n \thumanise_count(value, &entry->value, &entry->unit);\n+\tentry->concern = concern;\n \n \t/*\n \t * A NULL OID should not have a table annotation.\n@@ -445,6 +534,7 @@ static void stats_table_object_count_addf(struct stats_table *table,\n }\n \n static void stats_table_size_addf(struct stats_table *table, size_t value,\n+\t\t\t\t  size_t concern,\n \t\t\t\t  const char *format, ...)\n {\n \tstruct stats_table_entry *entry;\n@@ -452,6 +542,7 @@ static void stats_table_size_addf(struct stats_table *table, size_t value,\n \n \tCALLOC_ARRAY(entry, 1);\n \thumanise_bytes(value, &entry->value, &entry->unit, HUMANISE_COMPACT);\n+\tentry->concern = concern;\n \n \tva_start(ap, format);\n \tstats_table_vaddf(table, entry, format, ap);\n@@ -460,6 +551,7 @@ static void stats_table_size_addf(struct stats_table *table, size_t value,\n \n static void stats_table_object_size_addf(struct stats_table *table,\n \t\t\t\t\t struct object_id *oid, size_t value,\n+\t\t\t\t\t size_t concern,\n \t\t\t\t\t const char *format, ...)\n {\n \tstruct stats_table_entry *entry;\n@@ -467,6 +559,7 @@ static void stats_table_object_size_addf(struct stats_table *table,\n \n \tCALLOC_ARRAY(entry, 1);\n \thumanise_bytes(value, &entry->value, &entry->unit, HUMANISE_COMPACT);\n+\tentry->concern = concern;\n \n \t/*\n \t * A NULL OID should not have a table annotation.\n@@ -501,47 +594,104 @@ static void stats_table_setup_structure(struct stats_table *table,\n \n \tref_total = get_total_reference_count(refs);\n \tstats_table_addf(table, \"* %s\", _(\"References\"));\n-\tstats_table_count_addf(table, ref_total, \"  * %s\", _(\"Count\"));\n-\tstats_table_count_addf(table, refs->branches, \"    * %s\", _(\"Branches\"));\n-\tstats_table_count_addf(table, refs->tags, \"    * %s\", _(\"Tags\"));\n-\tstats_table_count_addf(table, refs->remotes, \"    * %s\", _(\"Remotes\"));\n-\tstats_table_count_addf(table, refs->others, \"    * %s\", _(\"Others\"));\n-\n+\tstats_table_count_addf(table, ref_total, CONCERN_STAT_VALUE_NONE,\n+\t\t\t       \"  * %s\", _(\"Count\"));\n+\tstats_table_count_addf(table, refs->branches, CONCERN_STAT_VALUE_NONE,\n+\t\t\t       \"    * %s\", _(\"Branches\"));\n+\tstats_table_count_addf(table, refs->tags,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_TAG_COUNT,\n+\t\t\t\t       refs->tags,\n+    \t\t\t               repo_structure_concern_thresholds),\n+\t\t\t       \"    * %s\", _(\"Tags\"));\n+\tstats_table_count_addf(table, refs->remotes, CONCERN_STAT_VALUE_NONE,\n+\t\t\t       \"    * %s\", _(\"Remotes\"));\n+\tstats_table_count_addf(table, refs->others, CONCERN_STAT_VALUE_NONE,\n+\t\t\t       \"    * %s\", _(\"Others\"));\n \tobject_count_total = get_total_object_values(&objects->type_counts);\n \tstats_table_addf(table, \"\");\n \tstats_table_addf(table, \"* %s\", _(\"Reachable objects\"));\n-\tstats_table_count_addf(table, object_count_total, \"  * %s\", _(\"Count\"));\n+\tstats_table_count_addf(table, object_count_total, CONCERN_STAT_VALUE_NONE,\n+\t\t\t       \"  * %s\", _(\"Count\"));\n \tstats_table_count_addf(table, objects->type_counts.commits,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_COMMIT_COUNT,\n+\t\t\t\t       objects->type_counts.commits,\n+\t\t\t\t       repo_structure_concern_thresholds),\n \t\t\t       \"    * %s\", _(\"Commits\"));\n \tstats_table_count_addf(table, objects->type_counts.trees,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_TREE_COUNT,\n+\t\t\t\t       objects->type_counts.trees,\n+\t\t\t\t       repo_structure_concern_thresholds),\n \t\t\t       \"    * %s\", _(\"Trees\"));\n \tstats_table_count_addf(table, objects->type_counts.blobs,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_BLOB_COUNT,\n+\t\t\t\t       objects->type_counts.blobs,\n+\t\t\t\t       repo_structure_concern_thresholds),\n \t\t\t       \"    * %s\", _(\"Blobs\"));\n \tstats_table_count_addf(table, objects->type_counts.tags,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_TAG_COUNT,\n+\t\t\t\t       objects->type_counts.tags,\n+\t\t\t\t       repo_structure_concern_thresholds),\n \t\t\t       \"    * %s\", _(\"Tags\"));\n \n \tinflated_object_total = get_total_object_values(&objects->inflated_sizes);\n-\tstats_table_size_addf(table, inflated_object_total,\n+\tstats_table_size_addf(table, inflated_object_total, CONCERN_STAT_VALUE_NONE,\n \t\t\t      \"  * %s\", _(\"Inflated size\"));\n \tstats_table_size_addf(table, objects->inflated_sizes.commits,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_COMMIT_INFLATED_SIZE,\n+\t\t\t\t      objects->inflated_sizes.commits,\n+\t\t\t\t      repo_structure_concern_thresholds),\n \t\t\t      \"    * %s\", _(\"Commits\"));\n \tstats_table_size_addf(table, objects->inflated_sizes.trees,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_TREE_INFLATED_SIZE,\n+\t\t\t\t      objects->inflated_sizes.trees,\n+\t\t\t\t      repo_structure_concern_thresholds),\n \t\t\t      \"    * %s\", _(\"Trees\"));\n \tstats_table_size_addf(table, objects->inflated_sizes.blobs,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_BLOB_INFLATED_SIZE,\n+\t\t\t\t      objects->inflated_sizes.blobs,\n+\t\t\t\t      repo_structure_concern_thresholds),\n \t\t\t      \"    * %s\", _(\"Blobs\"));\n \tstats_table_size_addf(table, objects->inflated_sizes.tags,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_TAG_INFLATED_SIZE,\n+\t\t\t\t      objects->inflated_sizes.tags,\n+\t\t\t\t      repo_structure_concern_thresholds),\n \t\t\t      \"    * %s\", _(\"Tags\"));\n \n \tdisk_object_total = get_total_object_values(&objects->disk_sizes);\n-\tstats_table_size_addf(table, disk_object_total,\n+\tstats_table_size_addf(table, disk_object_total, CONCERN_STAT_VALUE_NONE,\n \t\t\t      \"  * %s\", _(\"Disk size\"));\n \tstats_table_size_addf(table, objects->disk_sizes.commits,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_COMMIT_SIZE,\n+\t\t\t\t      objects->disk_sizes.commits,\n+\t\t\t\t      repo_structure_concern_thresholds),\n \t\t\t      \"    * %s\", _(\"Commits\"));\n \tstats_table_size_addf(table, objects->disk_sizes.trees,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_TREE_SIZE,\n+\t\t\t\t      objects->disk_sizes.trees,\n+\t\t\t\t      repo_structure_concern_thresholds),\n \t\t\t      \"    * %s\", _(\"Trees\"));\n \tstats_table_size_addf(table, objects->disk_sizes.blobs,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_BLOB_SIZE,\n+\t\t\t\t      objects->disk_sizes.blobs,\n+\t\t\t\t      repo_structure_concern_thresholds),\n \t\t\t      \"    * %s\", _(\"Blobs\"));\n \tstats_table_size_addf(table, objects->disk_sizes.tags,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_TAG_SIZE,\n+\t\t\t\t      objects->disk_sizes.tags,\n+\t\t\t\t      repo_structure_concern_thresholds),\n \t\t\t      \"    * %s\", _(\"Tags\"));\n \n \tstats_table_addf(table, \"\");\n@@ -550,51 +700,76 @@ static void stats_table_setup_structure(struct stats_table *table,\n \tstats_table_object_size_addf(table,\n \t\t\t\t     &objects->largest.commit_size.oid,\n \t\t\t\t     objects->largest.commit_size.value,\n+\t\t\t\t     repo_structure_concern_level(\n+\t\t\t\t\t     CONCERN_STAT_COMMIT_LARGEST,\n+\t\t\t\t\t     objects->largest.commit_size.value,\n+\t\t\t\t\t     repo_structure_concern_thresholds),\n \t\t\t\t     \"    * %s\", _(\"Maximum size\"));\n \tstats_table_object_count_addf(table,\n \t\t\t\t      &objects->largest.parent_count.oid,\n \t\t\t\t      objects->largest.parent_count.value,\n+\t\t\t\t      CONCERN_STAT_VALUE_NONE,\n \t\t\t\t      \"    * %s\", _(\"Maximum parents\"));\n \tstats_table_addf(table, \"  * %s\", _(\"Trees\"));\n \tstats_table_object_size_addf(table,\n \t\t\t\t     &objects->largest.tree_size.oid,\n \t\t\t\t     objects->largest.tree_size.value,\n+\t\t\t\t     repo_structure_concern_level(\n+\t\t\t\t\t     CONCERN_STAT_TREE_LARGEST,\n+\t\t\t\t\t     objects->largest.commit_size.value,\n+\t\t\t\t\t     repo_structure_concern_thresholds),\n \t\t\t\t     \"    * %s\", _(\"Maximum size\"));\n+\n \tstats_table_object_count_addf(table,\n-\t\t\t\t      &objects->largest.tree_entries.oid,\n-\t\t\t\t      objects->largest.tree_entries.value,\n-\t\t\t\t      \"    * %s\", _(\"Maximum entries\"));\n+\t\t\t\t     &objects->largest.tree_entries.oid,\n+\t\t\t\t     objects->largest.tree_entries.value,\n+\t\t\t             CONCERN_STAT_VALUE_NONE,\n+\t\t\t\t     \"    * %s\", _(\"Maximum entries\"));\n \tstats_table_addf(table, \"  * %s\", _(\"Blobs\"));\n \tstats_table_object_size_addf(table,\n \t\t\t\t     &objects->largest.blob_size.oid,\n \t\t\t\t     objects->largest.blob_size.value,\n+\t\t\t\t     repo_structure_concern_level(\n+\t\t\t\t\t     CONCERN_STAT_BLOB_LARGEST,\n+\t\t\t\t\t     objects->largest.blob_size.value,\n+\t\t\t\t\t     repo_structure_concern_thresholds),\n \t\t\t\t     \"    * %s\", _(\"Maximum size\"));\n \tstats_table_addf(table, \"  * %s\", _(\"Tags\"));\n \tstats_table_object_size_addf(table,\n \t\t\t\t     &objects->largest.tag_size.oid,\n \t\t\t\t     objects->largest.tag_size.value,\n+\t\t\t\t     repo_structure_concern_level(\n+\t\t\t\t\t     CONCERN_STAT_TAG_LARGEST,\n+\t\t\t\t\t     objects->largest.tag_size.value,\n+\t\t\t\t\t     repo_structure_concern_thresholds),\n \t\t\t\t     \"    * %s\", _(\"Maximum size\"));\n }\n \n #define INDEX_WIDTH 4\n \n-static void stats_table_print_structure(const struct stats_table *table)\n+static void stats_table_print_structure(const struct stats_table *table,\n+\t\t\t\t\tbool show_concern)\n {\n \tconst char *name_col_title = _(\"Repository structure\");\n \tconst char *value_col_title = _(\"Value\");\n+\tconst char *concern_col_title = _(\"Concern\");\n \tint title_name_width = utf8_strwidth(name_col_title);\n \tint title_value_width = utf8_strwidth(value_col_title);\n+\tint title_concern_width = utf8_strwidth(concern_col_title);\n \tint name_col_width = table->name_col_width;\n \tint value_col_width = table->value_col_width;\n \tint unit_col_width = table->unit_col_width;\n+\tint concern_col_width = table->concern_col_width;\n \tstruct string_list_item *item;\n \tstruct strbuf buf = STRBUF_INIT;\n+\tstruct strbuf concern_str = STRBUF_INIT;\n \n \tif (title_name_width > name_col_width)\n \t\tname_col_width = title_name_width;\n \tif (title_value_width > value_col_width + unit_col_width + 1)\n \t\tvalue_col_width = title_value_width - unit_col_width;\n-\n+\tif (show_concern && title_concern_width > concern_col_width)\n+\t\tconcern_col_width = title_concern_width;\n \tstrbuf_addstr(&buf, \"| \");\n \tstrbuf_utf8_align(&buf, ALIGN_LEFT, name_col_width + INDEX_WIDTH,\n \t\t\t  name_col_title);\n@@ -602,6 +777,12 @@ static void stats_table_print_structure(const struct stats_table *table)\n \tstrbuf_utf8_align(&buf, ALIGN_LEFT,\n \t\t\t  value_col_width + unit_col_width + 1, value_col_title);\n \tstrbuf_addstr(&buf, \" |\");\n+\tif (show_concern) {\n+\t\tstrbuf_addch(&buf, ' ');\n+\t\tstrbuf_utf8_align(&buf, ALIGN_LEFT, concern_col_width,\n+\t\t\t  concern_col_title);\n+\t\tstrbuf_addstr(&buf, \" |\");\n+\t}\n \tprintf(\"%s\\n\", buf.buf);\n \n \tprintf(\"| \");\n@@ -610,17 +791,28 @@ static void stats_table_print_structure(const struct stats_table *table)\n \tprintf(\" | \");\n \tfor (int i = 0; i < value_col_width + unit_col_width + 1; i++)\n \t\tputchar('-');\n-\tprintf(\" |\\n\");\n+\tprintf(\" |\");\n+\tif (show_concern) {\n+\t\tputchar(' ');\n+\t\tfor (int i = 0; i < concern_col_width; i++) {\n+\t\t\tputchar('-');\n+\t\t}\n+\t\tprintf(\" |\");\n+\t}\n+\tputchar('\\n');\n \n \tfor_each_string_list_item(item, &table->rows) {\n \t\tstruct stats_table_entry *entry = item->util;\n \t\tconst char *value = \"\";\n \t\tconst char *unit = \"\";\n+\t\tstrbuf_reset(&concern_str);\n \n \t\tif (entry) {\n \t\t\tvalue = entry->value;\n \t\t\tif (entry->unit)\n \t\t\t\tunit = entry->unit;\n+\t\t\tif (entry->concern > 0)\n+\t\t\t\tstrbuf_addchars(&concern_str, '*', entry->concern);\n \t\t}\n \n \t\tstrbuf_reset(&buf);\n@@ -638,6 +830,11 @@ static void stats_table_print_structure(const struct stats_table *table)\n \t\tstrbuf_addch(&buf, ' ');\n \t\tstrbuf_utf8_align(&buf, ALIGN_LEFT, unit_col_width, unit);\n \t\tstrbuf_addstr(&buf, \" |\");\n+\t\tif (show_concern) {\n+\t\t\tstrbuf_addch(&buf, ' ');\n+\t\t\tstrbuf_utf8_align(&buf, ALIGN_LEFT, concern_col_width, concern_str.buf);\n+\t\t\tstrbuf_addstr(&buf, \" |\");\n+\t\t}\n \t\tprintf(\"%s\\n\", buf.buf);\n \t}\n \n@@ -681,12 +878,15 @@ static void print_object_data(const char *key, char key_delim,\n }\n \n static void structure_keyvalue_print(struct repo_structure *stats,\n-\t\t\t\t     char key_delim, char value_delim)\n+\t\t\t\t     char key_delim, char value_delim,\n+\t\t\t\t     bool show_concern)\n {\n \tprint_keyvalue(\"references.branches.count\", key_delim,\n \t\t       stats->refs.branches, value_delim);\n+\n \tprint_keyvalue(\"references.tags.count\", key_delim,\n \t\t       stats->refs.tags, value_delim);\n+\n \tprint_keyvalue(\"references.remotes.count\", key_delim,\n \t\t       stats->refs.remotes, value_delim);\n \tprint_keyvalue(\"references.others.count\", key_delim,\n@@ -694,39 +894,153 @@ static void structure_keyvalue_print(struct repo_structure *stats,\n \n \tprint_keyvalue(\"objects.commits.count\", key_delim,\n \t\t       stats->objects.type_counts.commits, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.commits.count.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_COMMIT_COUNT,\n+\t\t\t\t       stats->objects.type_counts.commits,\n+\t\t\t\t       repo_structure_concern_thresholds),\n+\t\t\t       value_delim);\n \tprint_keyvalue(\"objects.trees.count\", key_delim,\n \t\t       stats->objects.type_counts.trees, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.trees.count.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_TREE_COUNT,\n+\t\t\t\t       stats->objects.type_counts.trees,\n+\t\t\t\t       repo_structure_concern_thresholds),\n+\t\t\t       value_delim);\n \tprint_keyvalue(\"objects.blobs.count\", key_delim,\n \t\t       stats->objects.type_counts.blobs, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.blobs.count.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_BLOB_COUNT,\n+\t\t\t\t       stats->objects.type_counts.blobs,\n+\t\t\t\t       repo_structure_concern_thresholds),\n+\t\t value_delim);\n \tprint_keyvalue(\"objects.tags.count\", key_delim,\n-\t\t       stats->objects.type_counts.tags, value_delim);\n+\t\tstats->objects.type_counts.tags, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.tags.count.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_TAG_COUNT,\n+\t\t\t\t       stats->objects.type_counts.tags,\n+\t\t\t\t       repo_structure_concern_thresholds),\n+\t\t \t       value_delim);\n \n \tprint_keyvalue(\"objects.commits.inflated_size\", key_delim,\n-\t\t       stats->objects.inflated_sizes.commits, value_delim);\n+\t\tstats->objects.inflated_sizes.commits, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.commits.inflated_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_COMMIT_INFLATED_SIZE,\n+\t\t\t\t       stats->objects.inflated_sizes.commits,\n+\t\t\t\t       repo_structure_concern_thresholds),\n+\t\t\t       value_delim);\n \tprint_keyvalue(\"objects.trees.inflated_size\", key_delim,\n-\t\t       stats->objects.inflated_sizes.trees, value_delim);\n+\t\tstats->objects.inflated_sizes.trees, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.trees.inflated_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_TREE_INFLATED_SIZE,\n+\t\t\t\t       stats->objects.inflated_sizes.trees,\n+\t\t\t\t       repo_structure_concern_thresholds),\n+\t\t\t       value_delim);\n+\n \tprint_keyvalue(\"objects.blobs.inflated_size\", key_delim,\n \t\t       stats->objects.inflated_sizes.blobs, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.blobs.inflated_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_BLOB_INFLATED_SIZE,\n+\t\t\t\t       stats->objects.inflated_sizes.blobs,\n+\t\t\t\t       repo_structure_concern_thresholds),\n+\t\t\t       value_delim);\n \tprint_keyvalue(\"objects.tags.inflated_size\", key_delim,\n \t\t       stats->objects.inflated_sizes.tags, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.tags.inflated_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_TAG_INFLATED_SIZE,\n+\t\t\t\t       stats->objects.inflated_sizes.tags,\n+\t\t\t\t       repo_structure_concern_thresholds),\n+\t\t\t       value_delim);\n \n \tprint_keyvalue(\"objects.commits.disk_size\", key_delim,\n \t\t       stats->objects.disk_sizes.commits, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.commits.disk_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_COMMIT_SIZE,\n+\t\t\t\t       stats->objects.disk_sizes.commits,\n+\t\t\t\t       repo_structure_concern_thresholds),\n+\t\t\t       value_delim);\n \tprint_keyvalue(\"objects.trees.disk_size\", key_delim,\n \t\t       stats->objects.disk_sizes.trees, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.trees.disk_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_TREE_SIZE,\n+\t\t\t\t       stats->objects.disk_sizes.trees,\n+\t\t\t\t       repo_structure_concern_thresholds),\n+\t\t\t       value_delim);\n \tprint_keyvalue(\"objects.blobs.disk_size\", key_delim,\n \t\t       stats->objects.disk_sizes.blobs, value_delim);\n+\tif (show_concern)\n+\n+\t\tprint_keyvalue(\"objects.blobs.disk_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_BLOB_SIZE,\n+\t\t\t\t       stats->objects.disk_sizes.blobs,\n+\t\t\t\t       repo_structure_concern_thresholds),\n+\t\t\t      value_delim);\n \tprint_keyvalue(\"objects.tags.disk_size\", key_delim,\n \t\t       stats->objects.disk_sizes.tags, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.tags.disk_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_TAG_SIZE,\n+\t\t\t\t       stats->objects.disk_sizes.tags,\n+\t\t\t\t       repo_structure_concern_thresholds),\n+\t\t\t      value_delim);\n \n \tprint_object_data(\"objects.commits.max_size\", key_delim,\n \t\t\t  &stats->objects.largest.commit_size, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.commits.max_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_COMMIT_LARGEST,\n+\t\t\t\t       stats->objects.largest.commit_size.value,\n+\t\t\t\t       repo_structure_concern_thresholds),\n+\t\t\t       value_delim);\n \tprint_object_data(\"objects.trees.max_size\", key_delim,\n \t\t\t  &stats->objects.largest.tree_size, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.trees.max_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_TREE_LARGEST,\n+\t\t\t\t       stats->objects.largest.tree_size.value,\n+\t\t\t\t       repo_structure_concern_thresholds),\n+\t\t\t       value_delim);\n \tprint_object_data(\"objects.blobs.max_size\", key_delim,\n \t\t\t  &stats->objects.largest.blob_size, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.blobs.max_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_BLOB_LARGEST,\n+\t\t\t\t       stats->objects.largest.blob_size.value,\n+\t\t\t\t       repo_structure_concern_thresholds),\n+\t\t\t       value_delim);\n \tprint_object_data(\"objects.tags.max_size\", key_delim,\n-\t\t\t  &stats->objects.largest.tag_size, value_delim);\n+\t\t          &stats->objects.largest.tag_size, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.tags.max_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_TAG_LARGEST,\n+\t\t\t\t       stats->objects.largest.tag_size.value,\n+\t\t\t\t       repo_structure_concern_thresholds),\n+\t\t\t       value_delim);\n \n \tprint_object_data(\"objects.commits.max_parents\", key_delim,\n \t\t\t  &stats->objects.largest.parent_count, value_delim);\n@@ -935,6 +1249,8 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,\n \tstruct repo_structure stats = { 0 };\n \tstruct rev_info revs;\n \tint show_progress = -1;\n+\tint show_concern = 0;\n+\n \tstruct option options[] = {\n \t\tOPT_CALLBACK_F(0, \"format\", &format, N_(\"format\"),\n \t\t\t       N_(\"output format\"),\n@@ -944,13 +1260,13 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,\n \t\t\t       PARSE_OPT_NONEG | PARSE_OPT_NOARG,\n \t\t\t       parse_format_cb),\n \t\tOPT_BOOL(0, \"progress\", &show_progress, N_(\"show progress\")),\n+\t\tOPT_BOOL(0, \"concern\", &show_concern, N_(\"show level of concern\")),\n \t\tOPT_END()\n \t};\n \n \targc = parse_options(argc, argv, prefix, options, repo_structure_usage, 0);\n \tif (argc)\n \t\tusage(_(\"too many arguments\"));\n-\n \trepo_init_revisions(repo, &revs, prefix);\n \n \tif (show_progress < 0)\n@@ -962,13 +1278,13 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,\n \tswitch (format) {\n \tcase FORMAT_TABLE:\n \t\tstats_table_setup_structure(&table, &stats);\n-\t\tstats_table_print_structure(&table);\n+\t\tstats_table_print_structure(&table, show_concern);\n \t\tbreak;\n \tcase FORMAT_NEWLINE_TERMINATED:\n-\t\tstructure_keyvalue_print(&stats, '=', '\\n');\n+\t\tstructure_keyvalue_print(&stats, '=', '\\n', show_concern);\n \t\tbreak;\n \tcase FORMAT_NUL_TERMINATED:\n-\t\tstructure_keyvalue_print(&stats, '\\n', '\\0');\n+\t\tstructure_keyvalue_print(&stats, '\\n', '\\0', show_concern);\n \t\tbreak;\n \tdefault:\n \t\tBUG(\"invalid output format\");\ndiff --git a/repo.c b/repo.c\nnew file mode 100644\nindex 0000000000..51f8b7042e\n--- /dev/null\n+++ b/repo.c\n@@ -0,0 +1 @@\n+#include \"repo.h\"\ndiff --git a/t/t1901-repo-structure.sh b/t/t1901-repo-structure.sh\nindex 02cc2b594a..f92168025b 100755\n--- a/t/t1901-repo-structure.sh\n+++ b/t/t1901-repo-structure.sh\n@@ -67,6 +67,7 @@ test_expect_success 'empty repository' '\n \t\tEOF\n \n \t\tgit repo structure >out 2>err &&\n+\t\tcp out /tmp/test1-out &&\n \n \t\ttest_cmp expect out &&\n \t\ttest_line_count = 0 err\n\n-- \n2.53.0\n\n\n"},{"id":"556808","messageId":"20261029-b4-repo-structure-concern-v4-2-d813537ec617@fastmail.com","threadId":"66621","inReplyTo":"20261029-b4-repo-structure-concern-v4-0-d813537ec617@fastmail.com","subject":"[PATCH v4 2/2] repo: make concern thresholds configurable","fromName":"Mark C. Chu-Carroll","fromEmail":"markchucarroll@fastmail.com","sentAt":"2026-10-29T17:52:23Z","receivedAt":"2026-10-29T17:52:23Z","isPatch":true,"sender":{"key":"markchucarroll@fastmail.com","avatar":null},"body":"The concern reporting mechanism so far provides a\nmeasure of concern based on heuristic values estimated\nby analyzing a collection of (hopefully) representative\nrepositories. But for different projects/usecases, the\nthresholds at which a value becomes concerning may vary.\n\nProvide a mechanism using git config to allow users to\nconfigure per-repo concern levels for \"git repo structure\"\nconcern reporting.\n\nSigned-off-by: Mark C. Chu-Carroll <markchucarroll@fastmail.com>\n---\n Documentation/config/repo.adoc |  59 ++++++++++++++++++\n Documentation/git-repo.adoc    |  11 ++++\n builtin/repo.c                 |  38 +++++++++++-\n t/t1901-repo-structure.sh      | 138 ++++++++++++++++++++++++++++++++++++++++-\n 4 files changed, 244 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/config/repo.adoc b/Documentation/config/repo.adoc\nnew file mode 100644\nindex 0000000000..811b099d72\n--- /dev/null\n+++ b/Documentation/config/repo.adoc\n@@ -0,0 +1,59 @@\n+`repo.concern.commit-count`::\n+\tSets the value of the concern threshold for commit count.\n+\n+`repo.concern.tree-count`::\n+\tSets the value of the concern threshold for tree count.\n+\n+`repo.concern.blob-count`::\n+\tSets the value of the concern threshold for blob count.\n+\n+`repo.concern.tag-count`::\n+\tSets the value of the concern threshold for tag count.\n+\n+`repo.concern.commit-size`::\n+\tSets the value of the concern threshold for total commit size.\n+\n+`repo.concern.tree-size`::\n+\tSets the value of the concern threshold for total tree size.\n+\n+`repo.concern.blob-size`::\n+\tSets the value of the concern threshold for total blob size.\n+\n+`repo.concern.tag-size`::\n+\tSets the value of the concern threshold for total tag size.\n+\n+`repo.concern.commit-inflated-size`::\n+\tSets the value of the concern threshold for total size\n+\tof all inflated commits.\n+\n+`repo.concern.tree-inflated-size`::\n+\tSets the value of the concern threshold for total size of\n+\tall inflated trees.\n+\n+`repo.concern.blob-inflated-size`::\n+\tSets the value of the concern threshold for total size of\n+\tall inflated blobs.\n+\n+`repo.concern.tag-inflated-size`::\n+\tSets the value of the concern threshold for the total size\n+\tof all inflated tags.\n+\n+`repo.concern.commit-largest`::\n+\tSets the value of the concern threshold the size of the largest\n+\tcommit.\n+\n+`repo.concern.tree-largest`::\n+\tSets the value of the concern threshold for the size of the\n+\tlargest tree.\n+\n+`repo.concern.blob-largest`::\n+\tSets the value of the concern threshold for the size of the\n+\tlargest blob.\n+\n+`repo.concern.tag-largest`::\n+\tSets the value of the concern threshold for the size of the\n+\tlargest tag.\n+\n+All of the concern thresholds are integer values for the\n+floor of the base-2 log of the threshold value. The default values\n+of each threshold are set to heuristically determined values.\ndiff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc\nindex f186b27d87..516403e17d 100644\n--- a/Documentation/git-repo.adoc\n+++ b/Documentation/git-repo.adoc\n@@ -101,6 +101,8 @@ statistic value. The value is considered concerning if it exceeds a heuristicall\n chosen threshold value; the degree of concern is the difference between the\n log_2 of the value, and the log_2 of the corresponding threshold.\n \n+The threshold values are configurable; see the configuration section below.\n+\n INFO KEYS\n ---------\n In order to obtain a set of values from `git repo info`, you should provide\n@@ -153,6 +155,15 @@ using the `nul` format:\n git repo info --format=nul layout.bare layout.shallow\n ------------\n \n+CONFIGURATION\n+-------------\n+\n+include::includes/cmd-config-section-all.adoc[]\n+\n+:git-repo: 1\n+include::config/repo.adoc[]\n+\n+\n SEE ALSO\n --------\n linkgit:git-rev-parse[1]\ndiff --git a/builtin/repo.c b/builtin/repo.c\nindex bd1866c5fa..4880c8b8f7 100644\n--- a/builtin/repo.c\n+++ b/builtin/repo.c\n@@ -2,6 +2,7 @@\n \n #include \"builtin.h\"\n #include \"commit.h\"\n+#include \"config.h\"\n #include \"environment.h\"\n #include \"hash.h\"\n #include \"hex.h\"\n@@ -30,6 +31,8 @@\n #define REPO_STRUCTURE_USAGE \\\n \t\"git repo structure [--format=(table|lines|nul) | -z] [--[no-]concern]\"\n \n+#define REPO_STRUCTURE_CONFIG_PREFIX \"repo.concern.\"\n+\n static const char *const repo_usage[] = {\n \tREPO_INFO_USAGE,\n \tREPO_STRUCTURE_USAGE,\n@@ -430,6 +433,35 @@ static size_t repo_structure_concern_level(const char* concern_name,\n \t\treturn 0;\n }\n \n+/*\n+ * Concern thresholds aren't universal. We try to set reasonable default\n+ * values, but projects may want to set these thresholds differently. To\n+ * support that, they can set the thresholds for their project in the\n+ * project's git config, in the section \"repo.structure.concern\".\n+  */\n+static int config_concern_thresholds(const char *prefixed_var,\n+\t\t\t\t     const char *str_val,\n+\t\t\t\t     const struct config_context *ctx,\n+\t\t\t\t     void *cb)\n+{\n+\tstruct concern_stat_threshold *thresholds = cb;\n+\tif (starts_with(prefixed_var, REPO_STRUCTURE_CONFIG_PREFIX)) {\n+\t\tconst char *concern_name = &prefixed_var[strlen(REPO_STRUCTURE_CONFIG_PREFIX)];\n+\t\tfor (int i = 0; thresholds[i].name; i++) {\n+\t\t\tif (!strcmp(thresholds[i].name, concern_name)) {\n+\t\t\t\tint value = git_config_int(prefixed_var, str_val, ctx->kvi);\n+\t\t\t\tif (value < 0)\n+\t\t\t\t\treturn -1;\n+\t\t\t\tthresholds[i].threshold = value;\n+\t\t\t\treturn 0;\n+\t\t\t}\n+\t\t}\n+\t\twarning(_(\"Invalid concern threshold name: '%s'\"), concern_name);\n+\t\treturn 0;\n+\t}\n+\treturn 0;\n+}\n+\n /*\n  * Holds column data that gets stored for each row.\n  */\n@@ -835,6 +867,7 @@ static void stats_table_print_structure(const struct stats_table *table,\n \t\t\tstrbuf_utf8_align(&buf, ALIGN_LEFT, concern_col_width, concern_str.buf);\n \t\t\tstrbuf_addstr(&buf, \" |\");\n \t\t}\n+\t\tstrbuf_release(&concern_str);\n \t\tprintf(\"%s\\n\", buf.buf);\n \t}\n \n@@ -1041,7 +1074,6 @@ static void structure_keyvalue_print(struct repo_structure *stats,\n \t\t\t\t       stats->objects.largest.tag_size.value,\n \t\t\t\t       repo_structure_concern_thresholds),\n \t\t\t       value_delim);\n-\n \tprint_object_data(\"objects.commits.max_parents\", key_delim,\n \t\t\t  &stats->objects.largest.parent_count, value_delim);\n \tprint_object_data(\"objects.trees.max_entries\", key_delim,\n@@ -1269,6 +1301,10 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,\n \t\tusage(_(\"too many arguments\"));\n \trepo_init_revisions(repo, &revs, prefix);\n \n+\tif (show_concern)\n+                repo_config(repo, &config_concern_thresholds, &repo_structure_concern_thresholds);\n+\n+\n \tif (show_progress < 0)\n \t\tshow_progress = isatty(2);\n \ndiff --git a/t/t1901-repo-structure.sh b/t/t1901-repo-structure.sh\nindex f92168025b..1db2cc5293 100755\n--- a/t/t1901-repo-structure.sh\n+++ b/t/t1901-repo-structure.sh\n@@ -67,7 +67,6 @@ test_expect_success 'empty repository' '\n \t\tEOF\n \n \t\tgit repo structure >out 2>err &&\n-\t\tcp out /tmp/test1-out &&\n \n \t\ttest_cmp expect out &&\n \t\ttest_line_count = 0 err\n@@ -145,6 +144,89 @@ test_expect_success SHA1 'repository with references and objects' '\n \t)\n '\n \n+test_expect_success SHA1 'concern flagging' '\n+\ttest_when_finished \"rm -rf repo\" &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\t# differences between zlib and zlib-ng can cause this\n+\t\t# test to fail; so we just turn it off.\n+\t\tgit config core.compression 0 &&\n+\t\t# configure thresholds to get concern indications\n+\t\tgit config repo.concern.commit-count 7 &&\n+\t\tgit config repo.concern.commit-size 14 &&\n+\t\tgit config repo.concern.tree-size 10 &&\n+\t\tgit config repo.concern.blob-size 8 &&\n+\t\tgit config repo.concern.blob-inflated-size 10 &&\n+\t\tgit config repo.concern.tag-size 2 &&\n+\t\tgit config repo.concern.commit-largest 4 &&\n+\t\tgit config repo.concern.blob-largest 2 &&\n+\t\ttest_commit_bulk 1005 &&\n+\t\tgit tag -a foo -m bar &&\n+\t\toid=\"$(git rev-parse HEAD)\" &&\n+\t\tgit update-ref refs/remotes/origin/foo \"$oid\" &&\n+\n+\t\t# Also creates a commit, tree, and blob.\n+\t\tgit notes add -m foo &&\n+\n+\n+\t\t# The tags disk size is handled specially due to the\n+\t\t# git-rev-list(1) --disk-usage=human option printing the full\n+\t\t# \"byte/bytes\" unit string instead of just \"B\".\n+\t\tcat >expect <<-EOF &&\n+\t\t| Repository structure      | Value      | Concern  |\n+\t\t| ------------------------- | ---------- | -------- |\n+\t\t| * References              |            |          |\n+\t\t|   * Count                 |      4     |          |\n+\t\t|     * Branches            |      1     |          |\n+\t\t|     * Tags                |      1     |          |\n+\t\t|     * Remotes             |      1     |          |\n+\t\t|     * Others              |      1     |          |\n+\t\t|                           |            |          |\n+\t\t| * Reachable objects       |            |          |\n+\t\t|   * Count                 |   3.02 k   |          |\n+\t\t|     * Commits             |   1.01 k   | **       |\n+\t\t|     * Trees               |   1.01 k   |          |\n+\t\t|     * Blobs               |   1.01 k   |          |\n+\t\t|     * Tags                |      1     |          |\n+\t\t|   * Inflated size         |  16.03 MiB |          |\n+\t\t|     * Commits             | 217.92 KiB |          |\n+\t\t|     * Trees               |  15.81 MiB |          |\n+\t\t|     * Blobs               |  11.68 KiB | ***      |\n+\t\t|     * Tags                |    132 B   |          |\n+\t\t|   * Disk size             | $(object_type_disk_usage all true) |          |\n+\t\t|     * Commits             | $(object_type_disk_usage commit true) | ***      |\n+\t\t|     * Trees               | $(object_type_disk_usage tree true) | ******** |\n+\t\t|     * Blobs               |  $(object_type_disk_usage blob true) | ******   |\n+\t\t|     * Tags                |    $(object_type_disk_usage tag) B   | *****    |\n+\t\t|                           |            |          |\n+\t\t| * Largest objects         |            |          |\n+\t\t|   * Commits               |            |          |\n+\t\t|     * Maximum size    [1] |    223 B   | ***      |\n+\t\t|     * Maximum parents [2] |      1     |          |\n+\t\t|   * Trees                 |            |          |\n+\t\t|     * Maximum size    [3] |  32.29 KiB |          |\n+\t\t|     * Maximum entries [4] |   1.01 k   |          |\n+\t\t|   * Blobs                 |            |          |\n+\t\t|     * Maximum size    [5] |     13 B   | *        |\n+\t\t|   * Tags                  |            |          |\n+\t\t|     * Maximum size    [6] |    132 B   |          |\n+\n+\t\t[1] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a\n+\t\t[2] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a\n+\t\t[3] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c\n+\t\t[4] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c\n+\t\t[5] 97d808e45116bf02103490294d3d46dad7a2ac62\n+\t\t[6] 4dae4f5954f5e6feb3577cfb1b181daa3fd3afd2\n+\t\tEOF\n+\n+\t\tgit repo structure --concern >actual 2>err &&\n+\n+\t\ttest_cmp expect actual &&\n+\t\ttest_line_count = 0 err\n+\t)\n+'\n+\n test_expect_success SHA1 'lines and nul format' '\n \ttest_when_finished \"rm -rf repo\" &&\n \tgit init repo &&\n@@ -205,6 +287,60 @@ test_expect_success SHA1 'lines and nul format' '\n \t)\n '\n \n+test_expect_success SHA1 'lines format with concerns' '\n+\ttest_when_finished \"rm -rf repo\" &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\t# configure thresholds to get concern indications\n+\t\tgit config core.compression 0 &&\n+\t\tgit config repo.concern.commit-count 7 &&\n+\t\tgit config repo.concern.commit-size 14 &&\n+\t\tgit config repo.concern.tree-size 10 &&\n+\t\tgit config repo.concern.blob-size 8 &&\n+\t\tgit config repo.concern.blob-inflated-size 10 &&\n+\t\tgit config repo.concern.tag-size 2 &&\n+\t\tgit config repo.concern.commit-largest 4 &&\n+\t\tgit config repo.concern.blob-largest 2 &&\n+\t\ttouch configured &&\n+\t\ttest_commit_bulk 1005 &&\n+\t\tgit tag -a foo -m bar &&\n+\t\toid=\"$(git rev-parse HEAD)\" &&\n+\t\tgit update-ref refs/remotes/origin/foo \"$oid\" &&\n+\n+\t\t# Also creates a commit, tree, and blob.\n+\t\tgit notes add -m foo &&\n+\n+\n+\t\t# The tags disk size is handled specially due to the\n+\t\t# git-rev-list(1) --disk-usage=human option printing the full\n+\t\t# \"byte/bytes\" unit string instead of just \"B\".\n+\t\tcat >expect <<-EOF &&\n+\t\tobjects.commits.count.concern_level=2\n+\t\tobjects.trees.count.concern_level=0\n+\t\tobjects.blobs.count.concern_level=0\n+\t\tobjects.tags.count.concern_level=0\n+\t\tobjects.commits.inflated_size.concern_level=0\n+\t\tobjects.trees.inflated_size.concern_level=0\n+\t\tobjects.blobs.inflated_size.concern_level=3\n+\t\tobjects.tags.inflated_size.concern_level=0\n+\t\tobjects.commits.disk_size.concern_level=3\n+\t\tobjects.trees.disk_size.concern_level=8\n+\t\tobjects.blobs.disk_size.concern_level=6\n+\t\tobjects.tags.disk_size.concern_level=5\n+\t\tobjects.commits.max_size.concern_level=3\n+\t\tobjects.trees.max_size.concern_level=0\n+\t\tobjects.blobs.max_size.concern_level=1\n+\t\tobjects.tags.max_size.concern_level=0\n+\t\tEOF\n+\n+\t\tgit repo structure --concern --format=lines >actual 2>err &&\n+\t\ttest_grep \"concern_level\" actual >actual-concerns &&\n+\t\ttest_cmp expect actual-concerns &&\n+\t\ttest_line_count = 0 err\n+\t)\n+'\n+\n test_expect_success 'progress meter option' '\n \ttest_when_finished \"rm -rf repo\" &&\n \tgit init repo &&\n\n-- \n2.53.0\n\n\n"},{"id":"556821","messageId":"xmqqld7g1dy4.fsf@gitster.g","threadId":"66621","inReplyTo":"20261029-b4-repo-structure-concern-v4-1-d813537ec617@fastmail.com","subject":"Re: [PATCH v4 1/2] repo: add level of concern reporting","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-10-29T19:53:07Z","receivedAt":"2026-10-29T19:53:07Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Mark C. Chu-Carroll\" <markchucarroll@fastmail.com> writes:\n\n> @@ -550,51 +700,76 @@ static void stats_table_setup_structure(struct stats_table *table,\n>  \tstats_table_object_size_addf(table,\n>  \t\t\t\t     &objects->largest.commit_size.oid,\n>  \t\t\t\t     objects->largest.commit_size.value,\n> +\t\t\t\t     repo_structure_concern_level(\n> +\t\t\t\t\t     CONCERN_STAT_COMMIT_LARGEST,\n> +\t\t\t\t\t     objects->largest.commit_size.value,\n> +\t\t\t\t\t     repo_structure_concern_thresholds),\n>  \t\t\t\t     \"    * %s\", _(\"Maximum size\"));\n\nSo, objects->largest.commit_size is computed using the value in\nobjects->largest.commit_size.value field.\n\n>  \tstats_table_object_size_addf(table,\n>  \t\t\t\t     &objects->largest.tree_size.oid,\n>  \t\t\t\t     objects->largest.tree_size.value,\n> +\t\t\t\t     repo_structure_concern_level(\n> +\t\t\t\t\t     CONCERN_STAT_TREE_LARGEST,\n> +\t\t\t\t\t     objects->largest.commit_size.value,\n> +\t\t\t\t\t     repo_structure_concern_thresholds),\n>  \t\t\t\t     \"    * %s\", _(\"Maximum size\"));\n\nAnd the same objects->largest.commit_size.value is also used to\ncompute objects->largest.tree_size here.\n\nCopy and paste mistake, perhaps?\n\nPlease do not take this \"review\" to mean that this is the only\nglitch I spotted, or that I will advocate for its inclusion in\n'next' once it is fixed.  I am merely pointing out what I happened\nto notice while driving by.\n\nI do not have a strong opinion for or against the feature itself,\nnor have I read the entire patch carefully.\n\nThanks.\n\n"},{"id":"556825","messageId":"DMHLN4FBDV5C.8O60G05LVV3@fastmail.com","threadId":"66621","inReplyTo":"xmqqld7g1dy4.fsf@gitster.g","subject":"Re: [PATCH v4 1/2] repo: add level of concern reporting","fromName":"Mark C. Chu-Carroll","fromEmail":"markchucarroll@fastmail.com","sentAt":"2026-10-29T20:20:10Z","receivedAt":"2026-10-29T20:20:10Z","isPatch":true,"sender":{"key":"markchucarroll@fastmail.com","avatar":null},"body":"On Thu Oct 29, 2026 at 3:53 PM EDT, Junio C Hamano wrote:\n> \"Mark C. Chu-Carroll\" <markchucarroll@fastmail.com> writes:\n>\n>> @@ -550,51 +700,76 @@ static void stats_table_setup_structure(struct stats_table *table,\n>>  \tstats_table_object_size_addf(table,\n>>  \t\t\t\t     &objects->largest.commit_size.oid,\n>>  \t\t\t\t     objects->largest.commit_size.value,\n>> +\t\t\t\t     repo_structure_concern_level(\n>> +\t\t\t\t\t     CONCERN_STAT_COMMIT_LARGEST,\n>> +\t\t\t\t\t     objects->largest.commit_size.value,\n>> +\t\t\t\t\t     repo_structure_concern_thresholds),\n>>  \t\t\t\t     \"    * %s\", _(\"Maximum size\"));\n>\n> So, objects->largest.commit_size is computed using the value in\n> objects->largest.commit_size.value field.\n>\n>>  \tstats_table_object_size_addf(table,\n>>  \t\t\t\t     &objects->largest.tree_size.oid,\n>>  \t\t\t\t     objects->largest.tree_size.value,\n>> +\t\t\t\t     repo_structure_concern_level(\n>> +\t\t\t\t\t     CONCERN_STAT_TREE_LARGEST,\n>> +\t\t\t\t\t     objects->largest.commit_size.value,\n>> +\t\t\t\t\t     repo_structure_concern_thresholds),\n>>  \t\t\t\t     \"    * %s\", _(\"Maximum size\"));\n>\n> And the same objects->largest.commit_size.value is also used to\n> compute objects->largest.tree_size here.\n>\n> Copy and paste mistake, perhaps?\n\nArgh, yeah. I road through this commit _so_ many times, and still \nmanaged to miss that. Grumble. Will, of course, fix.\n\n    -Mark\n\n\n\n-- \nMark Craig Chu-Carroll (@MarkChuCarroll at gitlab)\n*** Software Tools/Math Geek - Software Engineer at Gitlab\n*** Work Email: mcarroll@gitlab.com / markchucarroll@fastmail.com\n*** Personal Blog: http://goodmath.org/blog / Personal email: markcc@gmail.com\n\n\n"},{"id":"557201","messageId":"20261102-b4-repo-structure-concern-v5-0-2d8f37b70b75@fastmail.com","threadId":"66621","inReplyTo":"20261021-b4-repo-structure-concern-v1-0-e3010552292c@fastmail.com","subject":"[PATCH v5 0/2] repo: add degree-of-concern reporting to structure","fromName":"Mark C. Chu-Carroll","fromEmail":"markchucarroll@fastmail.com","sentAt":"2026-11-02T16:32:42Z","receivedAt":"2026-11-02T16:32:42Z","isPatch":true,"sender":{"key":"markchucarroll@fastmail.com","avatar":null},"body":"\"git repo structure\" computes a set of statistics that are useful for\ndiagnosing performance issues in a git repository. But for a typical\nuser who hasn't encountered any issues yet, it can be difficult to\ninterpret the data - when should a value be considered concerning?\n\nThis patchset introduces a feature to \"git repo structure\" that reports\non how concerning certain measures of storage structure  are for a\nrepository. In table mode, concern is displayed as a column containing\na number of stars indicating the degree of concern; in line or nul mode,\nconcerns are displayed as additional lines with a numeric value. This \nprovides a functionality equivalent to the concern column provided \nby \"git-sizer\", although the implementation is different.\n\nTo select defaults for the thresholds where a value becomes concern,\na (hopefully) representative selection of repositories were analyzed.\nBased on empirical exploration of the data, the measurement that seemed\nclosest to accurately assessing when a repository's structure becomes\nconcerning was based on the base 2 logarithm of the value. Across all of\nthe sample repositories, for all of the measurements, the point at which\nthings become concerning was when a base 2 logarithm of a statistic's\nvalue was more than 1 standard deviation from the mean of the log_2\nvalues of that stastistic in the sample set.  Based on this, the concern\nthreshold was set for each of the measures.\n\nWhile this seems like a reasonable approach for setting default\nthresholds, it's obviously still very ad-hoc. It's likely that over\ntime, the default values will evolve; and in any case, some projects\nwill want to set their own thresholds. To allow that, this includes\nsupport for configuring the thresholds on a per-repository basis.\n\nOverview of the change:\n         \n* The first commit adds the command-line option \"--concern\", and creates the basic implementation of concern reporting.\n* The next commit adds configurability for the concern thresholds.\n\nSigned-off-by: Mark C. Chu-Carroll <markchucarroll@fastmail.com>\n---\nChanges in v5:\n- whitespace fixes\n- change configuration values to make them more intuitive.\n- fix cut-and-paste error\n- fix location-of-deallocation error of strbuf in table generation\n- removed invalid concern for tag-refs.\n- fix integer typing in concern computation and table generation.\n- Link to v4: https://patch.msgid.link/20261029-b4-repo-structure-concern-v4-0-d813537ec617@fastmail.com\n\nChanges in v4:\n- Reworked the history; the previous round had a history-rewrite error.\n- Removed the duplication of configuration keys for the concern thresholds.\n- Changed the prefix of the config keys.\n- readability/quality of life naming changes.\n- Link to v3: https://patch.msgid.link/20261027-b4-repo-structure-concern-v3-0-98d7a5704170@fastmail.com\n\nChanges in v3:\n- Merge the documentation commit backwards into the other two commits.\n- Link to v2: https://patch.msgid.link/20261026-b4-repo-structure-concern-v2-0-e0df34228dc8@fastmail.com\n\nChanges in v2:\n- Fix memory leak in concern string generation.\n- Fix CI/CD test issue involving zlib versions.\n- Updating the naming of the config value prefix.\n- Link to v1: https://patch.msgid.link/20261021-b4-repo-structure-concern-v1-0-e3010552292c@fastmail.com\n\nTo: git@vger.kernel.org\n\n---\nMark C. Chu-Carroll (2):\n      repo: add level of concern reporting\n      repo: make concern thresholds configurable\n\n Documentation/config/repo.adoc |  63 ++++++\n Documentation/git-repo.adoc    |  27 ++-\n builtin/repo.c                 | 479 +++++++++++++++++++++++++++++++++++++----\n t/t1901-repo-structure.sh      | 138 +++++++++++-\n 4 files changed, 668 insertions(+), 39 deletions(-)\n\nRange-diff versus v4:\n\n1:  66d6fc14d4 ! 1:  c5100f5fa3 repo: add level of concern reporting\n    @@ Documentation/git-repo.adoc: supported:\n      ---------\n     \n      ## builtin/repo.c ##\n    +@@\n    + #define USE_THE_REPOSITORY_VARIABLE\n    + \n    ++#include \"git-compat-util.h\"\n    + #include \"builtin.h\"\n    + #include \"commit.h\"\n    ++#include \"config.h\"\n    + #include \"environment.h\"\n    + #include \"hash.h\"\n    + #include \"hex.h\"\n     @@\n      #include \"tree.h\"\n      #include \"tree-walk.h\"\n    @@ builtin/repo.c\n      #define REPO_STRUCTURE_USAGE \\\n     -\t\"git repo structure [--format=(table|lines|nul) | -z]\"\n     +\t\"git repo structure [--format=(table|lines|nul) | -z] [--[no-]concern]\"\n    ++\n    ++#define REPO_STRUCTURE_CONFIG_PREFIX \"repo.concern.\"\n      \n      static const char *const repo_usage[] = {\n      \tREPO_INFO_USAGE,\n    @@ builtin/repo.c: struct stats_table {\n      \tint name_col_width;\n      \tint value_col_width;\n      \tint unit_col_width;\n    -+\tint concern_col_width;\n    - };\n    - \n    ++\tsize_t max_concern_value;\n    ++};\n    ++\n     +/*\n     + * For computing level of concern about a stat, we need to have some\n    -+ * baseline values. For each of these, we'define a minimum level\n    ++ * baseline values. For each of these, we define a minimum level\n     + * of concern, which will appear as 1 star. Then we scale exponentially:\n     + * each time that value doubles, we add another star.\n     + *\n    @@ builtin/repo.c: struct stats_table {\n     +struct concern_stat_threshold {\n     +\tconst char *name;\n     +\tsize_t threshold;\n    -+};\n    -+\n    + };\n    + \n     +#define CONCERN_STAT_COMMIT_COUNT \"commit-count\"\n     +#define CONCERN_STAT_TREE_COUNT \"tree-count\"\n     +#define CONCERN_STAT_BLOB_COUNT \"blob-count\"\n    @@ builtin/repo.c: struct stats_table {\n     +#define CONCERN_STAT_BLOB_INFLATED_SIZE \"blob-inflated-size\"\n     +#define CONCERN_STAT_TAG_INFLATED_SIZE \"tag-inflated-size\"\n     +\n    -+struct concern_stat_threshold repo_structure_concern_thresholds[] = {\n    ++static struct concern_stat_threshold repo_structure_concern_thresholds[] = {\n     +\t{ .name = CONCERN_STAT_COMMIT_COUNT, .threshold = 19 },\n     +\t{ .name = CONCERN_STAT_TREE_COUNT, .threshold = 21 },\n     +\t{ .name = CONCERN_STAT_BLOB_COUNT, .threshold = 22 },\n    @@ builtin/repo.c: struct stats_table {\n     +\t{ 0 }\n     +};\n     +\n    -+static size_t get_concern_threshold(const char *name, struct concern_stat_threshold *thresholds) {\n    ++static int get_concern_threshold(const char *name, struct concern_stat_threshold *thresholds)\n    ++{\n     +\tfor (size_t i = 0; thresholds[i].name; i++) {\n    -+\t\tif (!strcmp(thresholds[i].name, name)) {\n    ++\t\tif (!strcmp(thresholds[i].name, name))\n     +\t\t\treturn thresholds[i].threshold;\n    -+\t\t}\n     +\t}\n    -+\treturn 0;\n    ++\treturn -1;\n     +}\n     +\n    -+static size_t repo_structure_concern_level(const char* concern_name,\n    -+\t\t\t\t\t   size_t value,\n    -+\t\t\t\t\t   struct concern_stat_threshold *thresholds) {\n    -+\tconst size_t threshold = get_concern_threshold(concern_name, thresholds);\n    -+\tsize_t log_value;\n    -+\tif (threshold == 0)\n    -+\t\tBUG(\"unexpected repository stat threshold: '%s'\", concern_name);\n    -+\tlog_value = log2u(value);\n    -+\tif (log_value > threshold)\n    -+\t\treturn log_value - threshold;\n    -+\telse\n    ++static int repo_structure_concern_level(const char *concern_name,\n    ++\t\t\t\t\tsize_t value,\n    ++\t\t\t\t\tstruct concern_stat_threshold *thresholds,\n    ++\t\t\t\t\tbool show_concern)\n    ++{\n    ++\tif (show_concern) {\n    ++\t\tsize_t log_value;\n    ++\t\tconst int threshold = get_concern_threshold(concern_name, thresholds);\n    ++\t\tif (threshold < 0)\n    ++\t\t\tBUG(\"unexpected repository stat threshold: '%s'\", concern_name);\n    ++\t\tlog_value = (size_t)log2u(value);\n    ++\t\tif (log_value > (size_t)threshold)\n    ++\t\t\treturn log_value - threshold;\n    ++\t\telse\n    ++\t\t\treturn 0;\n    ++\t} else {\n     +\t\treturn 0;\n    ++\t}\n     +}\n     +\n      /*\n    @@ builtin/repo.c: static void stats_table_vaddf(struct stats_table *table,\n      \t\tif (value_width > table->value_col_width)\n      \t\t\ttable->value_col_width = value_width;\n      \t}\n    -+\tif ((int)entry->concern > table->concern_col_width)\n    -+\t\ttable->concern_col_width = entry->concern;\n    ++\tif (entry->concern > table->max_concern_value)\n    ++\t\ttable->max_concern_value = entry->concern;\n    ++\n      \tif (entry->unit) {\n      \t\tint unit_width = utf8_strwidth(entry->unit);\n      \t\tif (unit_width > table->unit_col_width)\n    @@ builtin/repo.c: static void stats_table_addf(struct stats_table *table, const ch\n      \tva_end(ap);\n      }\n      \n    -+// define a symbol for 'no concern value for this stat'; this will\n    -+// make the code below easier to read.\n    ++/*\n    ++ * Define a symbol for 'no concern value for this stat'; this will\n    ++ * make the code below easier to read.\n    ++ */\n     +#define CONCERN_STAT_VALUE_NONE 0\n     +\n      static void stats_table_count_addf(struct stats_table *table, size_t value,\n    @@ builtin/repo.c: static void stats_table_object_size_addf(struct stats_table *tab\n      \n      \t/*\n      \t * A NULL OID should not have a table annotation.\n    +@@ builtin/repo.c: static inline size_t get_total_object_values(struct object_values *values)\n    + }\n    + \n    + static void stats_table_setup_structure(struct stats_table *table,\n    +-\t\t\t\t\tstruct repo_structure *stats)\n    ++\t\t\t\t\tstruct repo_structure *stats,\n    ++\t\t\t\t\tbool show_concern)\n    + {\n    + \tstruct object_stats *objects = &stats->objects;\n    + \tstruct ref_stats *refs = &stats->refs;\n     @@ builtin/repo.c: static void stats_table_setup_structure(struct stats_table *table,\n      \n      \tref_total = get_total_reference_count(refs);\n    @@ builtin/repo.c: static void stats_table_setup_structure(struct stats_table *tabl\n     +\tstats_table_count_addf(table, refs->branches, CONCERN_STAT_VALUE_NONE,\n     +\t\t\t       \"    * %s\", _(\"Branches\"));\n     +\tstats_table_count_addf(table, refs->tags,\n    -+\t\t\t       repo_structure_concern_level(\n    -+\t\t\t\t       CONCERN_STAT_TAG_COUNT,\n    -+\t\t\t\t       refs->tags,\n    -+    \t\t\t               repo_structure_concern_thresholds),\n    ++\t\t\t       CONCERN_STAT_VALUE_NONE,\n     +\t\t\t       \"    * %s\", _(\"Tags\"));\n     +\tstats_table_count_addf(table, refs->remotes, CONCERN_STAT_VALUE_NONE,\n     +\t\t\t       \"    * %s\", _(\"Remotes\"));\n    @@ builtin/repo.c: static void stats_table_setup_structure(struct stats_table *tabl\n     +\t\t\t       repo_structure_concern_level(\n     +\t\t\t\t       CONCERN_STAT_COMMIT_COUNT,\n     +\t\t\t\t       objects->type_counts.commits,\n    -+\t\t\t\t       repo_structure_concern_thresholds),\n    ++\t\t\t\t       repo_structure_concern_thresholds,\n    ++\t\t\t\t       show_concern),\n      \t\t\t       \"    * %s\", _(\"Commits\"));\n      \tstats_table_count_addf(table, objects->type_counts.trees,\n     +\t\t\t       repo_structure_concern_level(\n     +\t\t\t\t       CONCERN_STAT_TREE_COUNT,\n     +\t\t\t\t       objects->type_counts.trees,\n    -+\t\t\t\t       repo_structure_concern_thresholds),\n    ++\t\t\t\t       repo_structure_concern_thresholds,\n    ++\t\t\t\t       show_concern),\n      \t\t\t       \"    * %s\", _(\"Trees\"));\n      \tstats_table_count_addf(table, objects->type_counts.blobs,\n     +\t\t\t       repo_structure_concern_level(\n     +\t\t\t\t       CONCERN_STAT_BLOB_COUNT,\n     +\t\t\t\t       objects->type_counts.blobs,\n    -+\t\t\t\t       repo_structure_concern_thresholds),\n    ++\t\t\t\t       repo_structure_concern_thresholds,\n    ++\t\t\t               show_concern),\n      \t\t\t       \"    * %s\", _(\"Blobs\"));\n      \tstats_table_count_addf(table, objects->type_counts.tags,\n     +\t\t\t       repo_structure_concern_level(\n     +\t\t\t\t       CONCERN_STAT_TAG_COUNT,\n     +\t\t\t\t       objects->type_counts.tags,\n    -+\t\t\t\t       repo_structure_concern_thresholds),\n    ++\t\t\t\t       repo_structure_concern_thresholds,\n    ++\t\t\t\t       show_concern),\n      \t\t\t       \"    * %s\", _(\"Tags\"));\n      \n      \tinflated_object_total = get_total_object_values(&objects->inflated_sizes);\n    @@ builtin/repo.c: static void stats_table_setup_structure(struct stats_table *tabl\n     +\t\t\t      repo_structure_concern_level(\n     +\t\t\t\t      CONCERN_STAT_COMMIT_INFLATED_SIZE,\n     +\t\t\t\t      objects->inflated_sizes.commits,\n    -+\t\t\t\t      repo_structure_concern_thresholds),\n    ++\t\t\t\t      repo_structure_concern_thresholds,\n    ++\t\t\t\t      show_concern),\n      \t\t\t      \"    * %s\", _(\"Commits\"));\n      \tstats_table_size_addf(table, objects->inflated_sizes.trees,\n     +\t\t\t      repo_structure_concern_level(\n     +\t\t\t\t      CONCERN_STAT_TREE_INFLATED_SIZE,\n     +\t\t\t\t      objects->inflated_sizes.trees,\n    -+\t\t\t\t      repo_structure_concern_thresholds),\n    ++\t\t\t\t      repo_structure_concern_thresholds,\n    ++\t\t\t\t      show_concern),\n      \t\t\t      \"    * %s\", _(\"Trees\"));\n      \tstats_table_size_addf(table, objects->inflated_sizes.blobs,\n     +\t\t\t      repo_structure_concern_level(\n     +\t\t\t\t      CONCERN_STAT_BLOB_INFLATED_SIZE,\n     +\t\t\t\t      objects->inflated_sizes.blobs,\n    -+\t\t\t\t      repo_structure_concern_thresholds),\n    ++\t\t\t\t      repo_structure_concern_thresholds,\n    ++\t\t\t\t      show_concern),\n      \t\t\t      \"    * %s\", _(\"Blobs\"));\n      \tstats_table_size_addf(table, objects->inflated_sizes.tags,\n     +\t\t\t      repo_structure_concern_level(\n     +\t\t\t\t      CONCERN_STAT_TAG_INFLATED_SIZE,\n     +\t\t\t\t      objects->inflated_sizes.tags,\n    -+\t\t\t\t      repo_structure_concern_thresholds),\n    ++\t\t\t\t      repo_structure_concern_thresholds,\n    ++\t\t\t\t      show_concern),\n      \t\t\t      \"    * %s\", _(\"Tags\"));\n      \n      \tdisk_object_total = get_total_object_values(&objects->disk_sizes);\n    @@ builtin/repo.c: static void stats_table_setup_structure(struct stats_table *tabl\n     +\t\t\t      repo_structure_concern_level(\n     +\t\t\t\t      CONCERN_STAT_COMMIT_SIZE,\n     +\t\t\t\t      objects->disk_sizes.commits,\n    -+\t\t\t\t      repo_structure_concern_thresholds),\n    ++\t\t\t\t      repo_structure_concern_thresholds,\n    ++\t\t\t\t      show_concern),\n      \t\t\t      \"    * %s\", _(\"Commits\"));\n      \tstats_table_size_addf(table, objects->disk_sizes.trees,\n     +\t\t\t      repo_structure_concern_level(\n     +\t\t\t\t      CONCERN_STAT_TREE_SIZE,\n     +\t\t\t\t      objects->disk_sizes.trees,\n    -+\t\t\t\t      repo_structure_concern_thresholds),\n    ++\t\t\t\t      repo_structure_concern_thresholds,\n    ++\t\t\t\t      show_concern),\n      \t\t\t      \"    * %s\", _(\"Trees\"));\n      \tstats_table_size_addf(table, objects->disk_sizes.blobs,\n     +\t\t\t      repo_structure_concern_level(\n     +\t\t\t\t      CONCERN_STAT_BLOB_SIZE,\n     +\t\t\t\t      objects->disk_sizes.blobs,\n    -+\t\t\t\t      repo_structure_concern_thresholds),\n    ++\t\t\t\t      repo_structure_concern_thresholds,\n    ++\t\t\t\t      show_concern),\n      \t\t\t      \"    * %s\", _(\"Blobs\"));\n      \tstats_table_size_addf(table, objects->disk_sizes.tags,\n     +\t\t\t      repo_structure_concern_level(\n     +\t\t\t\t      CONCERN_STAT_TAG_SIZE,\n     +\t\t\t\t      objects->disk_sizes.tags,\n    -+\t\t\t\t      repo_structure_concern_thresholds),\n    ++\t\t\t\t      repo_structure_concern_thresholds,\n    ++\t\t\t\t      show_concern),\n      \t\t\t      \"    * %s\", _(\"Tags\"));\n      \n      \tstats_table_addf(table, \"\");\n    @@ builtin/repo.c: static void stats_table_setup_structure(struct stats_table *tabl\n     +\t\t\t\t     repo_structure_concern_level(\n     +\t\t\t\t\t     CONCERN_STAT_COMMIT_LARGEST,\n     +\t\t\t\t\t     objects->largest.commit_size.value,\n    -+\t\t\t\t\t     repo_structure_concern_thresholds),\n    ++\t\t\t\t\t     repo_structure_concern_thresholds,\n    ++\t\t\t\t\t     show_concern),\n      \t\t\t\t     \"    * %s\", _(\"Maximum size\"));\n      \tstats_table_object_count_addf(table,\n      \t\t\t\t      &objects->largest.parent_count.oid,\n    @@ builtin/repo.c: static void stats_table_setup_structure(struct stats_table *tabl\n      \t\t\t\t     objects->largest.tree_size.value,\n     +\t\t\t\t     repo_structure_concern_level(\n     +\t\t\t\t\t     CONCERN_STAT_TREE_LARGEST,\n    -+\t\t\t\t\t     objects->largest.commit_size.value,\n    -+\t\t\t\t\t     repo_structure_concern_thresholds),\n    ++\t\t\t\t\t     objects->largest.tree_size.value,\n    ++\t\t\t\t\t     repo_structure_concern_thresholds,\n    ++\t\t\t\t\t     show_concern),\n      \t\t\t\t     \"    * %s\", _(\"Maximum size\"));\n     +\n      \tstats_table_object_count_addf(table,\n    @@ builtin/repo.c: static void stats_table_setup_structure(struct stats_table *tabl\n     +\t\t\t\t     repo_structure_concern_level(\n     +\t\t\t\t\t     CONCERN_STAT_BLOB_LARGEST,\n     +\t\t\t\t\t     objects->largest.blob_size.value,\n    -+\t\t\t\t\t     repo_structure_concern_thresholds),\n    ++\t\t\t\t\t     repo_structure_concern_thresholds,\n    ++\t\t\t\t\t     show_concern),\n      \t\t\t\t     \"    * %s\", _(\"Maximum size\"));\n      \tstats_table_addf(table, \"  * %s\", _(\"Tags\"));\n      \tstats_table_object_size_addf(table,\n    @@ builtin/repo.c: static void stats_table_setup_structure(struct stats_table *tabl\n     +\t\t\t\t     repo_structure_concern_level(\n     +\t\t\t\t\t     CONCERN_STAT_TAG_LARGEST,\n     +\t\t\t\t\t     objects->largest.tag_size.value,\n    -+\t\t\t\t\t     repo_structure_concern_thresholds),\n    ++\t\t\t\t\t     repo_structure_concern_thresholds,\n    ++\t\t\t\t\t     show_concern),\n      \t\t\t\t     \"    * %s\", _(\"Maximum size\"));\n      }\n      \n    @@ builtin/repo.c: static void stats_table_setup_structure(struct stats_table *tabl\n      {\n      \tconst char *name_col_title = _(\"Repository structure\");\n      \tconst char *value_col_title = _(\"Value\");\n    -+\tconst char *concern_col_title = _(\"Concern\");\n    - \tint title_name_width = utf8_strwidth(name_col_title);\n    - \tint title_value_width = utf8_strwidth(value_col_title);\n    -+\tint title_concern_width = utf8_strwidth(concern_col_title);\n    +@@ builtin/repo.c: static void stats_table_print_structure(const struct stats_table *table)\n      \tint name_col_width = table->name_col_width;\n      \tint value_col_width = table->value_col_width;\n      \tint unit_col_width = table->unit_col_width;\n    -+\tint concern_col_width = table->concern_col_width;\n    ++\tconst char *concern_col_title = _(\"Concern\");\n    ++\tconst size_t concern_title_width = strlen(concern_col_title);\n    ++\tsize_t concern_col_width = (table->max_concern_value > concern_title_width) ? table->max_concern_value : concern_title_width;\n      \tstruct string_list_item *item;\n      \tstruct strbuf buf = STRBUF_INIT;\n     +\tstruct strbuf concern_str = STRBUF_INIT;\n    @@ builtin/repo.c: static void stats_table_setup_structure(struct stats_table *tabl\n      \tif (title_value_width > value_col_width + unit_col_width + 1)\n      \t\tvalue_col_width = title_value_width - unit_col_width;\n     -\n    -+\tif (show_concern && title_concern_width > concern_col_width)\n    -+\t\tconcern_col_width = title_concern_width;\n      \tstrbuf_addstr(&buf, \"| \");\n      \tstrbuf_utf8_align(&buf, ALIGN_LEFT, name_col_width + INDEX_WIDTH,\n      \t\t\t  name_col_title);\n    @@ builtin/repo.c: static void stats_table_print_structure(const struct stats_table\n     +\tprintf(\" |\");\n     +\tif (show_concern) {\n     +\t\tputchar(' ');\n    -+\t\tfor (int i = 0; i < concern_col_width; i++) {\n    ++\t\tfor (size_t i = 0; i < concern_col_width; i++) {\n     +\t\t\tputchar('-');\n     +\t\t}\n     +\t\tprintf(\" |\");\n    @@ builtin/repo.c: static void stats_table_print_structure(const struct stats_table\n      \t\tprintf(\"%s\\n\", buf.buf);\n      \t}\n      \n    +@@ builtin/repo.c: static void stats_table_print_structure(const struct stats_table *table)\n    + \t\tfor_each_string_list_item(item, &table->annotations)\n    + \t\t\tprintf(\"%s\\n\", item->string);\n    + \t}\n    +-\n    ++\tstrbuf_release(&concern_str);\n    + \tstrbuf_release(&buf);\n    + }\n    + \n     @@ builtin/repo.c: static void print_object_data(const char *key, char key_delim,\n      }\n      \n    @@ builtin/repo.c: static void print_object_data(const char *key, char key_delim,\n      {\n      \tprint_keyvalue(\"references.branches.count\", key_delim,\n      \t\t       stats->refs.branches, value_delim);\n    -+\n    - \tprint_keyvalue(\"references.tags.count\", key_delim,\n    - \t\t       stats->refs.tags, value_delim);\n    -+\n    - \tprint_keyvalue(\"references.remotes.count\", key_delim,\n    +@@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stats,\n      \t\t       stats->refs.remotes, value_delim);\n      \tprint_keyvalue(\"references.others.count\", key_delim,\n    -@@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stats,\n    - \n    + \t\t       stats->refs.others, value_delim);\n    +-\n      \tprint_keyvalue(\"objects.commits.count\", key_delim,\n      \t\t       stats->objects.type_counts.commits, value_delim);\n     +\tif (show_concern)\n    @@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stat\n     +\t\t\t       repo_structure_concern_level(\n     +\t\t\t\t       CONCERN_STAT_COMMIT_COUNT,\n     +\t\t\t\t       stats->objects.type_counts.commits,\n    -+\t\t\t\t       repo_structure_concern_thresholds),\n    ++\t\t\t\t       repo_structure_concern_thresholds,\n    ++\t\t\t\t       show_concern),\n     +\t\t\t       value_delim);\n      \tprint_keyvalue(\"objects.trees.count\", key_delim,\n      \t\t       stats->objects.type_counts.trees, value_delim);\n    @@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stat\n     +\t\t\t       repo_structure_concern_level(\n     +\t\t\t\t       CONCERN_STAT_TREE_COUNT,\n     +\t\t\t\t       stats->objects.type_counts.trees,\n    -+\t\t\t\t       repo_structure_concern_thresholds),\n    ++\t\t\t\t       repo_structure_concern_thresholds,\n    ++\t\t\t\t       show_concern),\n     +\t\t\t       value_delim);\n      \tprint_keyvalue(\"objects.blobs.count\", key_delim,\n      \t\t       stats->objects.type_counts.blobs, value_delim);\n    @@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stat\n     +\t\t\t       repo_structure_concern_level(\n     +\t\t\t\t       CONCERN_STAT_BLOB_COUNT,\n     +\t\t\t\t       stats->objects.type_counts.blobs,\n    -+\t\t\t\t       repo_structure_concern_thresholds),\n    ++\t\t\t\t       repo_structure_concern_thresholds,\n    ++\t\t\t\t       show_concern),\n     +\t\t value_delim);\n      \tprint_keyvalue(\"objects.tags.count\", key_delim,\n     -\t\t       stats->objects.type_counts.tags, value_delim);\n    +-\n     +\t\tstats->objects.type_counts.tags, value_delim);\n     +\tif (show_concern)\n     +\t\tprint_keyvalue(\"objects.tags.count.concern_level\", key_delim,\n     +\t\t\t       repo_structure_concern_level(\n     +\t\t\t\t       CONCERN_STAT_TAG_COUNT,\n     +\t\t\t\t       stats->objects.type_counts.tags,\n    -+\t\t\t\t       repo_structure_concern_thresholds),\n    -+\t\t \t       value_delim);\n    - \n    ++\t\t\t\t       repo_structure_concern_thresholds,\n    ++\t\t\t\t       show_concern),\n    ++\t\t\t       value_delim);\n      \tprint_keyvalue(\"objects.commits.inflated_size\", key_delim,\n     -\t\t       stats->objects.inflated_sizes.commits, value_delim);\n     +\t\tstats->objects.inflated_sizes.commits, value_delim);\n    @@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stat\n     +\t\t\t       repo_structure_concern_level(\n     +\t\t\t\t       CONCERN_STAT_COMMIT_INFLATED_SIZE,\n     +\t\t\t\t       stats->objects.inflated_sizes.commits,\n    -+\t\t\t\t       repo_structure_concern_thresholds),\n    ++\t\t\t\t       repo_structure_concern_thresholds,\n    ++\t\t\t\t       show_concern),\n     +\t\t\t       value_delim);\n      \tprint_keyvalue(\"objects.trees.inflated_size\", key_delim,\n     -\t\t       stats->objects.inflated_sizes.trees, value_delim);\n    @@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stat\n     +\tif (show_concern)\n     +\t\tprint_keyvalue(\"objects.trees.inflated_size.concern_level\", key_delim,\n     +\t\t\t       repo_structure_concern_level(\n    -+\t\t\t\t       CONCERN_STAT_TREE_INFLATED_SIZE,\n    -+\t\t\t\t       stats->objects.inflated_sizes.trees,\n    -+\t\t\t\t       repo_structure_concern_thresholds),\n    ++\t\t\t\t\tCONCERN_STAT_TREE_INFLATED_SIZE,\n    ++\t\t\t\t\tstats->objects.inflated_sizes.trees,\n    ++\t\t\t\t\trepo_structure_concern_thresholds,\n    ++\t\t\t\t\tshow_concern),\n     +\t\t\t       value_delim);\n    -+\n      \tprint_keyvalue(\"objects.blobs.inflated_size\", key_delim,\n      \t\t       stats->objects.inflated_sizes.blobs, value_delim);\n     +\tif (show_concern)\n    @@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stat\n     +\t\t\t       repo_structure_concern_level(\n     +\t\t\t\t       CONCERN_STAT_BLOB_INFLATED_SIZE,\n     +\t\t\t\t       stats->objects.inflated_sizes.blobs,\n    -+\t\t\t\t       repo_structure_concern_thresholds),\n    ++\t\t\t\t       repo_structure_concern_thresholds,\n    ++\t\t\t\t       show_concern),\n     +\t\t\t       value_delim);\n      \tprint_keyvalue(\"objects.tags.inflated_size\", key_delim,\n      \t\t       stats->objects.inflated_sizes.tags, value_delim);\n    +-\n     +\tif (show_concern)\n     +\t\tprint_keyvalue(\"objects.tags.inflated_size.concern_level\", key_delim,\n     +\t\t\t       repo_structure_concern_level(\n     +\t\t\t\t       CONCERN_STAT_TAG_INFLATED_SIZE,\n     +\t\t\t\t       stats->objects.inflated_sizes.tags,\n    -+\t\t\t\t       repo_structure_concern_thresholds),\n    ++\t\t\t\t       repo_structure_concern_thresholds,\n    ++\t\t\t\t       show_concern),\n     +\t\t\t       value_delim);\n    - \n      \tprint_keyvalue(\"objects.commits.disk_size\", key_delim,\n      \t\t       stats->objects.disk_sizes.commits, value_delim);\n     +\tif (show_concern)\n    @@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stat\n     +\t\t\t       repo_structure_concern_level(\n     +\t\t\t\t       CONCERN_STAT_COMMIT_SIZE,\n     +\t\t\t\t       stats->objects.disk_sizes.commits,\n    -+\t\t\t\t       repo_structure_concern_thresholds),\n    ++\t\t\t\t       repo_structure_concern_thresholds,\n    ++\t\t\t\t       show_concern),\n     +\t\t\t       value_delim);\n      \tprint_keyvalue(\"objects.trees.disk_size\", key_delim,\n      \t\t       stats->objects.disk_sizes.trees, value_delim);\n    @@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stat\n     +\t\t\t       repo_structure_concern_level(\n     +\t\t\t\t       CONCERN_STAT_TREE_SIZE,\n     +\t\t\t\t       stats->objects.disk_sizes.trees,\n    -+\t\t\t\t       repo_structure_concern_thresholds),\n    ++\t\t\t\t       repo_structure_concern_thresholds,\n    ++\t\t\t\t       show_concern),\n     +\t\t\t       value_delim);\n      \tprint_keyvalue(\"objects.blobs.disk_size\", key_delim,\n      \t\t       stats->objects.disk_sizes.blobs, value_delim);\n     +\tif (show_concern)\n    -+\n     +\t\tprint_keyvalue(\"objects.blobs.disk_size.concern_level\", key_delim,\n     +\t\t\t       repo_structure_concern_level(\n     +\t\t\t\t       CONCERN_STAT_BLOB_SIZE,\n     +\t\t\t\t       stats->objects.disk_sizes.blobs,\n    -+\t\t\t\t       repo_structure_concern_thresholds),\n    ++\t\t\t\t       repo_structure_concern_thresholds,\n    ++\t\t\t\t       show_concern),\n     +\t\t\t      value_delim);\n      \tprint_keyvalue(\"objects.tags.disk_size\", key_delim,\n      \t\t       stats->objects.disk_sizes.tags, value_delim);\n    +-\n     +\tif (show_concern)\n     +\t\tprint_keyvalue(\"objects.tags.disk_size.concern_level\", key_delim,\n     +\t\t\t       repo_structure_concern_level(\n     +\t\t\t\t       CONCERN_STAT_TAG_SIZE,\n     +\t\t\t\t       stats->objects.disk_sizes.tags,\n    -+\t\t\t\t       repo_structure_concern_thresholds),\n    ++\t\t\t\t       repo_structure_concern_thresholds,\n    ++\t\t\t\t       show_concern),\n     +\t\t\t      value_delim);\n    - \n      \tprint_object_data(\"objects.commits.max_size\", key_delim,\n      \t\t\t  &stats->objects.largest.commit_size, value_delim);\n     +\tif (show_concern)\n    @@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stat\n     +\t\t\t       repo_structure_concern_level(\n     +\t\t\t\t       CONCERN_STAT_COMMIT_LARGEST,\n     +\t\t\t\t       stats->objects.largest.commit_size.value,\n    -+\t\t\t\t       repo_structure_concern_thresholds),\n    ++\t\t\t\t       repo_structure_concern_thresholds,\n    ++\t\t\t\t       show_concern),\n     +\t\t\t       value_delim);\n      \tprint_object_data(\"objects.trees.max_size\", key_delim,\n      \t\t\t  &stats->objects.largest.tree_size, value_delim);\n    @@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stat\n     +\t\t\t       repo_structure_concern_level(\n     +\t\t\t\t       CONCERN_STAT_TREE_LARGEST,\n     +\t\t\t\t       stats->objects.largest.tree_size.value,\n    -+\t\t\t\t       repo_structure_concern_thresholds),\n    ++\t\t\t\t       repo_structure_concern_thresholds,\n    ++\t\t\t\t       show_concern),\n     +\t\t\t       value_delim);\n      \tprint_object_data(\"objects.blobs.max_size\", key_delim,\n      \t\t\t  &stats->objects.largest.blob_size, value_delim);\n    @@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stat\n     +\t\t\t       repo_structure_concern_level(\n     +\t\t\t\t       CONCERN_STAT_BLOB_LARGEST,\n     +\t\t\t\t       stats->objects.largest.blob_size.value,\n    -+\t\t\t\t       repo_structure_concern_thresholds),\n    ++\t\t\t\t       repo_structure_concern_thresholds,\n    ++\t\t\t\t       show_concern),\n     +\t\t\t       value_delim);\n      \tprint_object_data(\"objects.tags.max_size\", key_delim,\n     -\t\t\t  &stats->objects.largest.tag_size, value_delim);\n    +-\n     +\t\t          &stats->objects.largest.tag_size, value_delim);\n     +\tif (show_concern)\n     +\t\tprint_keyvalue(\"objects.tags.max_size.concern_level\", key_delim,\n     +\t\t\t       repo_structure_concern_level(\n     +\t\t\t\t       CONCERN_STAT_TAG_LARGEST,\n     +\t\t\t\t       stats->objects.largest.tag_size.value,\n    -+\t\t\t\t       repo_structure_concern_thresholds),\n    ++\t\t\t\t       repo_structure_concern_thresholds,\n    ++\t\t\t\t       show_concern),\n     +\t\t\t       value_delim);\n    - \n      \tprint_object_data(\"objects.commits.max_parents\", key_delim,\n      \t\t\t  &stats->objects.largest.parent_count, value_delim);\n    + \tprint_object_data(\"objects.trees.max_entries\", key_delim,\n    + \t\t\t  &stats->objects.largest.tree_entries, value_delim);\n    +-\n    + \tfflush(stdout);\n    + }\n    + \n     @@ builtin/repo.c: static int cmd_repo_structure(int argc, const char **argv, const char *prefix,\n      \tstruct repo_structure stats = { 0 };\n      \tstruct rev_info revs;\n    @@ builtin/repo.c: static int cmd_repo_structure(int argc, const char **argv, const\n      \n      \tif (show_progress < 0)\n     @@ builtin/repo.c: static int cmd_repo_structure(int argc, const char **argv, const char *prefix,\n    + \n      \tswitch (format) {\n      \tcase FORMAT_TABLE:\n    - \t\tstats_table_setup_structure(&table, &stats);\n    +-\t\tstats_table_setup_structure(&table, &stats);\n     -\t\tstats_table_print_structure(&table);\n    ++\t\tstats_table_setup_structure(&table, &stats, show_concern);\n     +\t\tstats_table_print_structure(&table, show_concern);\n      \t\tbreak;\n      \tcase FORMAT_NEWLINE_TERMINATED:\n    @@ builtin/repo.c: static int cmd_repo_structure(int argc, const char **argv, const\n      \tdefault:\n      \t\tBUG(\"invalid output format\");\n     \n    - ## repo.c (new) ##\n    -@@\n    -+#include \"repo.h\"\n    -\n      ## t/t1901-repo-structure.sh ##\n     @@ t/t1901-repo-structure.sh: test_expect_success 'empty repository' '\n    + \t\t|   * Tags                  |        |\n    + \t\t|     * Maximum size        |    0 B |\n      \t\tEOF\n    - \n    +-\n      \t\tgit repo structure >out 2>err &&\n    -+\t\tcp out /tmp/test1-out &&\n      \n      \t\ttest_cmp expect out &&\n    - \t\ttest_line_count = 0 err\n2:  2c1395b6f0 ! 2:  0a3b6415ec repo: make concern thresholds configurable\n    @@ Documentation/config/repo.adoc (new)\n     +\tof all inflated tags.\n     +\n     +`repo.concern.commit-largest`::\n    -+\tSets the value of the concern threshold the size of the largest\n    ++\tSets the value of the concern threshold for the size of the largest\n     +\tcommit.\n     +\n     +`repo.concern.tree-largest`::\n    @@ Documentation/config/repo.adoc (new)\n     +\tSets the value of the concern threshold for the size of the\n     +\tlargest tag.\n     +\n    -+All of the concern thresholds are integer values for the\n    -+floor of the base-2 log of the threshold value. The default values\n    -+of each threshold are set to heuristically determined values.\n    ++Thresholds are actually computed using log values. But for human convenience,\n    ++they're set as integer values. If the integer value supplied isn't a power of\n    ++two, the actual value will be rounded to the nearest power of two. For example,\n    ++if the value in the configuration setting is 80,000, the actual threshold will\n    ++be set to 65536; if instead, the value from the configuration was set to\n    ++100,000, the threshold will be set to 131,072.\n    ++\n     \n      ## Documentation/git-repo.adoc ##\n     @@ Documentation/git-repo.adoc: statistic value. The value is considered concerning if it exceeds a heuristicall\n    @@ Documentation/git-repo.adoc: using the `nul` format:\n      linkgit:git-rev-parse[1]\n     \n      ## builtin/repo.c ##\n    -@@\n    - \n    - #include \"builtin.h\"\n    - #include \"commit.h\"\n    -+#include \"config.h\"\n    - #include \"environment.h\"\n    - #include \"hash.h\"\n    - #include \"hex.h\"\n    -@@\n    - #define REPO_STRUCTURE_USAGE \\\n    - \t\"git repo structure [--format=(table|lines|nul) | -z] [--[no-]concern]\"\n    - \n    -+#define REPO_STRUCTURE_CONFIG_PREFIX \"repo.concern.\"\n    -+\n    - static const char *const repo_usage[] = {\n    - \tREPO_INFO_USAGE,\n    - \tREPO_STRUCTURE_USAGE,\n    -@@ builtin/repo.c: static size_t repo_structure_concern_level(const char* concern_name,\n    - \t\treturn 0;\n    +@@ builtin/repo.c: static int repo_structure_concern_level(const char *concern_name,\n    + \t\tconst int threshold = get_concern_threshold(concern_name, thresholds);\n    + \t\tif (threshold < 0)\n    + \t\t\tBUG(\"unexpected repository stat threshold: '%s'\", concern_name);\n    ++\t\tif (!threshold)\n    ++\t\t\treturn 0;\n    + \t\tlog_value = (size_t)log2u(value);\n    + \t\tif (log_value > (size_t)threshold)\n    + \t\t\treturn log_value - threshold;\n    +@@ builtin/repo.c: static int repo_structure_concern_level(const char *concern_name,\n    + \t}\n      }\n      \n    ++/*\n    ++ * To make configuring thresholds as intuitive as possible, we set the\n    ++ * internal threshold value to the _nearest_ power of two to the value\n    ++ * the user specified. So if they set a threshold to 500, we should use\n    ++ * a log value of 9, but log2u would return 8.\n    ++ *\n    ++ * A threshold value of 0 means \"never flag concerns for this stat\".\n    ++ */\n    ++static size_t nearest_log2_value(size_t v)\n    ++{\n    ++\tsize_t logval, power_of_two_floor, mid;\n    ++\tif (!v)\n    ++\t\treturn 0;\n    ++\tlogval = (size_t)log2u(v);\n    ++\tpower_of_two_floor = (size_t)1 << logval;\n    ++\tmid = (power_of_two_floor + (power_of_two_floor << 1)) / 2;\n    ++\treturn (v <= mid) ? logval : (logval + 1);\n    ++}\n    ++\n     +/*\n     + * Concern thresholds aren't universal. We try to set reasonable default\n     + * values, but projects may want to set these thresholds differently. To\n     + * support that, they can set the thresholds for their project in the\n    -+ * project's git config, in the section \"repo.structure.concern\".\n    -+  */\n    -+static int config_concern_thresholds(const char *prefixed_var,\n    ++ * project's git config, in the section \"repo.concern\".\n    ++ *\n    ++ * This is used by repo_config; the call site is guarded by show_concern,\n    ++ * so the global threshold array is only modified when --concern is requested,\n    ++ * and that will happen at most once.\n    ++ */\n    ++static int config_concern_thresholds(const char *config_val_name,\n     +\t\t\t\t     const char *str_val,\n     +\t\t\t\t     const struct config_context *ctx,\n     +\t\t\t\t     void *cb)\n     +{\n     +\tstruct concern_stat_threshold *thresholds = cb;\n    -+\tif (starts_with(prefixed_var, REPO_STRUCTURE_CONFIG_PREFIX)) {\n    -+\t\tconst char *concern_name = &prefixed_var[strlen(REPO_STRUCTURE_CONFIG_PREFIX)];\n    ++\tconst char *concern_name;\n    ++\tif (skip_prefix(config_val_name, REPO_STRUCTURE_CONFIG_PREFIX, &concern_name)) {\n     +\t\tfor (int i = 0; thresholds[i].name; i++) {\n     +\t\t\tif (!strcmp(thresholds[i].name, concern_name)) {\n    -+\t\t\t\tint value = git_config_int(prefixed_var, str_val, ctx->kvi);\n    -+\t\t\t\tif (value < 0)\n    -+\t\t\t\t\treturn -1;\n    -+\t\t\t\tthresholds[i].threshold = value;\n    ++\t\t\t\tint threshold = git_config_int(concern_name, str_val, ctx->kvi);\n    ++\t\t\t\tif (threshold < 0) {\n    ++\t\t\t\t\twarning(_(\"invalid concern threshold '%d': value must be 0 or more\"),\n    ++\t\t\t\t\t\tthreshold);\n    ++\t\t\t\t\treturn 0;\n    ++\t\t\t\t}\n    ++\t\t\t\tthresholds[i].threshold = nearest_log2_value(threshold);\n     +\t\t\t\treturn 0;\n     +\t\t\t}\n     +\t\t}\n    -+\t\twarning(_(\"Invalid concern threshold name: '%s'\"), concern_name);\n    -+\t\treturn 0;\n    ++\t\twarning(_(\"invalid concern threshold name: '%s'\"), concern_name);\n     +\t}\n     +\treturn 0;\n     +}\n    @@ builtin/repo.c: static size_t repo_structure_concern_level(const char* concern_n\n      /*\n       * Holds column data that gets stored for each row.\n       */\n    -@@ builtin/repo.c: static void stats_table_print_structure(const struct stats_table *table,\n    - \t\t\tstrbuf_utf8_align(&buf, ALIGN_LEFT, concern_col_width, concern_str.buf);\n    - \t\t\tstrbuf_addstr(&buf, \" |\");\n    - \t\t}\n    -+\t\tstrbuf_release(&concern_str);\n    - \t\tprintf(\"%s\\n\", buf.buf);\n    - \t}\n    - \n    -@@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stats,\n    - \t\t\t\t       stats->objects.largest.tag_size.value,\n    - \t\t\t\t       repo_structure_concern_thresholds),\n    - \t\t\t       value_delim);\n    --\n    - \tprint_object_data(\"objects.commits.max_parents\", key_delim,\n    - \t\t\t  &stats->objects.largest.parent_count, value_delim);\n    - \tprint_object_data(\"objects.trees.max_entries\", key_delim,\n     @@ builtin/repo.c: static int cmd_repo_structure(int argc, const char **argv, const char *prefix,\n      \t\tusage(_(\"too many arguments\"));\n      \trepo_init_revisions(repo, &revs, prefix);\n      \n     +\tif (show_concern)\n    -+                repo_config(repo, &config_concern_thresholds, &repo_structure_concern_thresholds);\n    -+\n    ++\t\trepo_config(repo, &config_concern_thresholds,\n    ++\t\t\t    &repo_structure_concern_thresholds);\n     +\n      \tif (show_progress < 0)\n      \t\tshow_progress = isatty(2);\n      \n     \n      ## t/t1901-repo-structure.sh ##\n    -@@ t/t1901-repo-structure.sh: test_expect_success 'empty repository' '\n    - \t\tEOF\n    - \n    - \t\tgit repo structure >out 2>err &&\n    --\t\tcp out /tmp/test1-out &&\n    - \n    - \t\ttest_cmp expect out &&\n    - \t\ttest_line_count = 0 err\n     @@ t/t1901-repo-structure.sh: test_expect_success SHA1 'repository with references and objects' '\n      \t)\n      '\n    @@ t/t1901-repo-structure.sh: test_expect_success SHA1 'repository with references\n     +\t\t# test to fail; so we just turn it off.\n     +\t\tgit config core.compression 0 &&\n     +\t\t# configure thresholds to get concern indications\n    -+\t\tgit config repo.concern.commit-count 7 &&\n    -+\t\tgit config repo.concern.commit-size 14 &&\n    -+\t\tgit config repo.concern.tree-size 10 &&\n    -+\t\tgit config repo.concern.blob-size 8 &&\n    -+\t\tgit config repo.concern.blob-inflated-size 10 &&\n    -+\t\tgit config repo.concern.tag-size 2 &&\n    -+\t\tgit config repo.concern.commit-largest 4 &&\n    -+\t\tgit config repo.concern.blob-largest 2 &&\n    ++\t\tgit config repo.concern.commit-count 120 &&\n    ++\t\tgit config repo.concern.commit-size 16384 &&\n    ++\t\tgit config repo.concern.tree-size 1024 &&\n    ++\t\tgit config repo.concern.blob-size 256 &&\n    ++\t\tgit config repo.concern.blob-inflated-size 1024 &&\n    ++\t\tgit config repo.concern.tag-size 4 &&\n    ++\t\tgit config repo.concern.commit-largest 16 &&\n    ++\t\tgit config repo.concern.blob-largest 4 &&\n     +\t\ttest_commit_bulk 1005 &&\n     +\t\tgit tag -a foo -m bar &&\n     +\t\toid=\"$(git rev-parse HEAD)\" &&\n    @@ t/t1901-repo-structure.sh: test_expect_success SHA1 'lines and nul format' '\n     +\t\tcd repo &&\n     +\t\t# configure thresholds to get concern indications\n     +\t\tgit config core.compression 0 &&\n    -+\t\tgit config repo.concern.commit-count 7 &&\n    -+\t\tgit config repo.concern.commit-size 14 &&\n    -+\t\tgit config repo.concern.tree-size 10 &&\n    -+\t\tgit config repo.concern.blob-size 8 &&\n    -+\t\tgit config repo.concern.blob-inflated-size 10 &&\n    -+\t\tgit config repo.concern.tag-size 2 &&\n    -+\t\tgit config repo.concern.commit-largest 4 &&\n    -+\t\tgit config repo.concern.blob-largest 2 &&\n    ++\t\tgit config repo.concern.commit-count 100 &&\n    ++\t\tgit config repo.concern.commit-size 16384 &&\n    ++\t\tgit config repo.concern.tree-size 1024 &&\n    ++\t\tgit config repo.concern.blob-size 256 &&\n    ++\t\tgit config repo.concern.blob-inflated-size 1024 &&\n    ++\t\tgit config repo.concern.tag-size 4 &&\n    ++\t\tgit config repo.concern.commit-largest 16 &&\n    ++\t\tgit config repo.concern.blob-largest 4 &&\n     +\t\ttouch configured &&\n     +\t\ttest_commit_bulk 1005 &&\n     +\t\tgit tag -a foo -m bar &&\n\n---\nbase-commit: 3bc0341126508f78f5869cbfc0005e987efdf0c7\nchange-id: 20261019-b4-repo-structure-concern-f1d6f6326095\n\n\n"},{"id":"557202","messageId":"20261102-b4-repo-structure-concern-v5-1-2d8f37b70b75@fastmail.com","threadId":"66621","inReplyTo":"20261102-b4-repo-structure-concern-v5-0-2d8f37b70b75@fastmail.com","subject":"[PATCH v5 1/2] repo: add level of concern reporting","fromName":"Mark C. Chu-Carroll","fromEmail":"markchucarroll@fastmail.com","sentAt":"2026-11-02T16:32:43Z","receivedAt":"2026-11-02T16:32:43Z","isPatch":true,"sender":{"key":"markchucarroll@fastmail.com","avatar":null},"body":"\"git repo structure\" computes a set of statistics\nthat are useful for diagnosing performance issues in\na git repository. But for a typical user who hasn't\nencountered any issues yet, it can be difficult to\ninterpret the data - when should a value be considered\nconcerning?\n\nAdd a new option to \"git repo structure\" which causes it\nto generate an additional column providing a level of concern\nfor many values.\n\nTo select defaults for the thresholds where a value becomes concern,\na (hopefully) representative selection of repositories were analyzed.\nBased on empirical exploration of the data, the measurement that seemed\nclosest to accurately assessing when a repository's structure becomes\nconcerning was based on the base 2 logarithm of the value. Across all of\nthe sample repositories, for all of the measurements, the point at which\nthings become problematic was when a base 2 logarithm of a value was\nmore than 1 standard deviation from the mean of the log_2 values in\nthe sample set.\n\nSigned-off-by: Mark C. Chu-Carroll <markchucarroll@fastmail.com>\n---\n Documentation/git-repo.adoc |  16 +-\n builtin/repo.c              | 419 ++++++++++++++++++++++++++++++++++++++++----\n t/t1901-repo-structure.sh   |   1 -\n 3 files changed, 397 insertions(+), 39 deletions(-)\n\ndiff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc\nindex ed7d80c690..f186b27d87 100644\n--- a/Documentation/git-repo.adoc\n+++ b/Documentation/git-repo.adoc\n@@ -10,7 +10,7 @@ SYNOPSIS\n [synopsis]\n git repo info [--format=(lines|nul) | -z] [--all | <key>...]\n git repo info --keys [--format=(lines|nul) | -z]\n-git repo structure [--format=(table|lines|nul) | -z]\n+git repo structure [--format=(table|lines|nul) | -z] [--[no-]concern]\n \n DESCRIPTION\n -----------\n@@ -56,7 +56,7 @@ supported:\n `nul`:::\n \tSimilar to `lines`, but using a _NUL_ character after each value.\n \n-`structure [--format=(table|lines|nul) | -z]`::\n+`structure [--format=(table|lines|nul) | -z] [--[no-]concern]`::\n \tRetrieve statistics about the current repository structure. The\n \tfollowing kinds of information are reported:\n +\n@@ -88,6 +88,18 @@ supported:\n \tquoted.\n +\n `-z` is an alias for `--format=nul`.\n++\n+To make it easier to know when one of the values is something that\n+users should be concerned about, adding '--concern' will provide a\n+level of concern to be added to the output. In 'table' mode, this will\n+be a table column indicating the level of concern as a number of stars;\n+in `lines` or `nul` mode, a `concern_level` line will be added for each\n+statistic with a numeric value.\n++\n+The level of concern for each statistic is computed using the log_2 of the\n+statistic value. The value is considered concerning if it exceeds a heuristically\n+chosen threshold value; the degree of concern is the difference between the\n+log_2 of the value, and the log_2 of the corresponding threshold.\n \n INFO KEYS\n ---------\ndiff --git a/builtin/repo.c b/builtin/repo.c\nindex 84e012f83f..7a50605779 100644\n--- a/builtin/repo.c\n+++ b/builtin/repo.c\n@@ -1,7 +1,9 @@\n #define USE_THE_REPOSITORY_VARIABLE\n \n+#include \"git-compat-util.h\"\n #include \"builtin.h\"\n #include \"commit.h\"\n+#include \"config.h\"\n #include \"environment.h\"\n #include \"hash.h\"\n #include \"hex.h\"\n@@ -21,13 +23,16 @@\n #include \"tree.h\"\n #include \"tree-walk.h\"\n #include \"utf8.h\"\n+#include \"wrapper.h\"\n \n #define REPO_INFO_USAGE \\\n \t\"git repo info [--format=(lines|nul) | -z] [--all | <key>...]\", \\\n \t\"git repo info --keys [--format=(lines|nul) | -z]\"\n \n #define REPO_STRUCTURE_USAGE \\\n-\t\"git repo structure [--format=(table|lines|nul) | -z]\"\n+\t\"git repo structure [--format=(table|lines|nul) | -z] [--[no-]concern]\"\n+\n+#define REPO_STRUCTURE_CONFIG_PREFIX \"repo.concern.\"\n \n static const char *const repo_usage[] = {\n \tREPO_INFO_USAGE,\n@@ -348,8 +353,93 @@ struct stats_table {\n \tint name_col_width;\n \tint value_col_width;\n \tint unit_col_width;\n+\tsize_t max_concern_value;\n+};\n+\n+/*\n+ * For computing level of concern about a stat, we need to have some\n+ * baseline values. For each of these, we define a minimum level\n+ * of concern, which will appear as 1 star. Then we scale exponentially:\n+ * each time that value doubles, we add another star.\n+ *\n+ * The threshold values for concern are expressed as log_2 values of the size\n+ * at which the statistic becomes concerning. The default values for the\n+ * threshold were selected based on observed values from a collection of\n+ * representative-ish repositories of varying size, by finding the mean log_2\n+ * value and standard deviation (sigma) for each statistic over the collection.\n+ * The threshold for each metric is then set at mean(log_2) + sigma.\n+ */\n+struct concern_stat_threshold {\n+\tconst char *name;\n+\tsize_t threshold;\n };\n \n+#define CONCERN_STAT_COMMIT_COUNT \"commit-count\"\n+#define CONCERN_STAT_TREE_COUNT \"tree-count\"\n+#define CONCERN_STAT_BLOB_COUNT \"blob-count\"\n+#define CONCERN_STAT_TAG_COUNT \"tag-count\"\n+#define CONCERN_STAT_COMMIT_SIZE \"commit-size\"\n+#define CONCERN_STAT_TREE_SIZE \"tree-size\"\n+#define CONCERN_STAT_BLOB_SIZE \"blob-size\"\n+#define CONCERN_STAT_TAG_SIZE \"tag-size\"\n+#define CONCERN_STAT_COMMIT_LARGEST \"commit-largest\"\n+#define CONCERN_STAT_TREE_LARGEST \"tree-largest\"\n+#define CONCERN_STAT_BLOB_LARGEST \"blob-largest\"\n+#define CONCERN_STAT_TAG_LARGEST \"tag-largest\"\n+#define CONCERN_STAT_COMMIT_INFLATED_SIZE \"commit-inflated-size\"\n+#define CONCERN_STAT_TREE_INFLATED_SIZE \"tree-inflated-size\"\n+#define CONCERN_STAT_BLOB_INFLATED_SIZE \"blob-inflated-size\"\n+#define CONCERN_STAT_TAG_INFLATED_SIZE \"tag-inflated-size\"\n+\n+static struct concern_stat_threshold repo_structure_concern_thresholds[] = {\n+\t{ .name = CONCERN_STAT_COMMIT_COUNT, .threshold = 19 },\n+\t{ .name = CONCERN_STAT_TREE_COUNT, .threshold = 21 },\n+\t{ .name = CONCERN_STAT_BLOB_COUNT, .threshold = 22 },\n+\t{ .name = CONCERN_STAT_TAG_COUNT, .threshold = 15 },\n+\t{ .name = CONCERN_STAT_COMMIT_SIZE, .threshold = 28 },\n+\t{ .name = CONCERN_STAT_TREE_SIZE, .threshold = 27 },\n+\t{ .name = CONCERN_STAT_BLOB_SIZE, .threshold = 36 },\n+\t{ .name = CONCERN_STAT_TAG_SIZE, .threshold = 18 },\n+\t{ .name = CONCERN_STAT_COMMIT_LARGEST, .threshold = 18  },\n+\t{ .name = CONCERN_STAT_TREE_LARGEST, .threshold = 18 },\n+\t{ .name = CONCERN_STAT_BLOB_LARGEST, .threshold = 26 },\n+\t{ .name = CONCERN_STAT_TAG_LARGEST, .threshold =  15 },\n+\t{ .name = CONCERN_STAT_COMMIT_INFLATED_SIZE, .threshold = 28 },\n+\t{ .name = CONCERN_STAT_TREE_INFLATED_SIZE, .threshold = 32 },\n+\t{ .name = CONCERN_STAT_BLOB_INFLATED_SIZE, .threshold = 34 },\n+\t{ .name = CONCERN_STAT_TAG_INFLATED_SIZE, .threshold = 25 },\n+\t{ 0 }\n+};\n+\n+static int get_concern_threshold(const char *name, struct concern_stat_threshold *thresholds)\n+{\n+\tfor (size_t i = 0; thresholds[i].name; i++) {\n+\t\tif (!strcmp(thresholds[i].name, name))\n+\t\t\treturn thresholds[i].threshold;\n+\t}\n+\treturn -1;\n+}\n+\n+static int repo_structure_concern_level(const char *concern_name,\n+\t\t\t\t\tsize_t value,\n+\t\t\t\t\tstruct concern_stat_threshold *thresholds,\n+\t\t\t\t\tbool show_concern)\n+{\n+\tif (show_concern) {\n+\t\tsize_t log_value;\n+\t\tconst int threshold = get_concern_threshold(concern_name, thresholds);\n+\t\tif (threshold < 0)\n+\t\t\tBUG(\"unexpected repository stat threshold: '%s'\", concern_name);\n+\t\tlog_value = (size_t)log2u(value);\n+\t\tif (log_value > (size_t)threshold)\n+\t\t\treturn log_value - threshold;\n+\t\telse\n+\t\t\treturn 0;\n+\t} else {\n+\t\treturn 0;\n+\t}\n+}\n+\n /*\n  * Holds column data that gets stored for each row.\n  */\n@@ -358,6 +448,7 @@ struct stats_table_entry {\n \tconst char *unit;\n \tsize_t index;\n \tstruct object_id *oid;\n+\tsize_t concern;\n };\n \n static void stats_table_vaddf(struct stats_table *table,\n@@ -391,6 +482,9 @@ static void stats_table_vaddf(struct stats_table *table,\n \t\tif (value_width > table->value_col_width)\n \t\t\ttable->value_col_width = value_width;\n \t}\n+\tif (entry->concern > table->max_concern_value)\n+\t\ttable->max_concern_value = entry->concern;\n+\n \tif (entry->unit) {\n \t\tint unit_width = utf8_strwidth(entry->unit);\n \t\tif (unit_width > table->unit_col_width)\n@@ -409,14 +503,21 @@ static void stats_table_addf(struct stats_table *table, const char *format, ...)\n \tva_end(ap);\n }\n \n+/*\n+ * Define a symbol for 'no concern value for this stat'; this will\n+ * make the code below easier to read.\n+ */\n+#define CONCERN_STAT_VALUE_NONE 0\n+\n static void stats_table_count_addf(struct stats_table *table, size_t value,\n-\t\t\t\t   const char *format, ...)\n+\t\t\t\t   size_t concern, const char *format, ...)\n {\n \tstruct stats_table_entry *entry;\n \tva_list ap;\n \n \tCALLOC_ARRAY(entry, 1);\n \thumanise_count(value, &entry->value, &entry->unit);\n+\tentry->concern = concern;\n \n \tva_start(ap, format);\n \tstats_table_vaddf(table, entry, format, ap);\n@@ -425,13 +526,14 @@ static void stats_table_count_addf(struct stats_table *table, size_t value,\n \n static void stats_table_object_count_addf(struct stats_table *table,\n \t\t\t\t\t  struct object_id *oid, size_t value,\n-\t\t\t\t\t  const char *format, ...)\n+\t\t\t\t\t  size_t concern, const char *format, ...)\n {\n \tstruct stats_table_entry *entry;\n \tva_list ap;\n \n \tCALLOC_ARRAY(entry, 1);\n \thumanise_count(value, &entry->value, &entry->unit);\n+\tentry->concern = concern;\n \n \t/*\n \t * A NULL OID should not have a table annotation.\n@@ -445,6 +547,7 @@ static void stats_table_object_count_addf(struct stats_table *table,\n }\n \n static void stats_table_size_addf(struct stats_table *table, size_t value,\n+\t\t\t\t  size_t concern,\n \t\t\t\t  const char *format, ...)\n {\n \tstruct stats_table_entry *entry;\n@@ -452,6 +555,7 @@ static void stats_table_size_addf(struct stats_table *table, size_t value,\n \n \tCALLOC_ARRAY(entry, 1);\n \thumanise_bytes(value, &entry->value, &entry->unit, HUMANISE_COMPACT);\n+\tentry->concern = concern;\n \n \tva_start(ap, format);\n \tstats_table_vaddf(table, entry, format, ap);\n@@ -460,6 +564,7 @@ static void stats_table_size_addf(struct stats_table *table, size_t value,\n \n static void stats_table_object_size_addf(struct stats_table *table,\n \t\t\t\t\t struct object_id *oid, size_t value,\n+\t\t\t\t\t size_t concern,\n \t\t\t\t\t const char *format, ...)\n {\n \tstruct stats_table_entry *entry;\n@@ -467,6 +572,7 @@ static void stats_table_object_size_addf(struct stats_table *table,\n \n \tCALLOC_ARRAY(entry, 1);\n \thumanise_bytes(value, &entry->value, &entry->unit, HUMANISE_COMPACT);\n+\tentry->concern = concern;\n \n \t/*\n \t * A NULL OID should not have a table annotation.\n@@ -490,7 +596,8 @@ static inline size_t get_total_object_values(struct object_values *values)\n }\n \n static void stats_table_setup_structure(struct stats_table *table,\n-\t\t\t\t\tstruct repo_structure *stats)\n+\t\t\t\t\tstruct repo_structure *stats,\n+\t\t\t\t\tbool show_concern)\n {\n \tstruct object_stats *objects = &stats->objects;\n \tstruct ref_stats *refs = &stats->refs;\n@@ -501,47 +608,113 @@ static void stats_table_setup_structure(struct stats_table *table,\n \n \tref_total = get_total_reference_count(refs);\n \tstats_table_addf(table, \"* %s\", _(\"References\"));\n-\tstats_table_count_addf(table, ref_total, \"  * %s\", _(\"Count\"));\n-\tstats_table_count_addf(table, refs->branches, \"    * %s\", _(\"Branches\"));\n-\tstats_table_count_addf(table, refs->tags, \"    * %s\", _(\"Tags\"));\n-\tstats_table_count_addf(table, refs->remotes, \"    * %s\", _(\"Remotes\"));\n-\tstats_table_count_addf(table, refs->others, \"    * %s\", _(\"Others\"));\n-\n+\tstats_table_count_addf(table, ref_total, CONCERN_STAT_VALUE_NONE,\n+\t\t\t       \"  * %s\", _(\"Count\"));\n+\tstats_table_count_addf(table, refs->branches, CONCERN_STAT_VALUE_NONE,\n+\t\t\t       \"    * %s\", _(\"Branches\"));\n+\tstats_table_count_addf(table, refs->tags,\n+\t\t\t       CONCERN_STAT_VALUE_NONE,\n+\t\t\t       \"    * %s\", _(\"Tags\"));\n+\tstats_table_count_addf(table, refs->remotes, CONCERN_STAT_VALUE_NONE,\n+\t\t\t       \"    * %s\", _(\"Remotes\"));\n+\tstats_table_count_addf(table, refs->others, CONCERN_STAT_VALUE_NONE,\n+\t\t\t       \"    * %s\", _(\"Others\"));\n \tobject_count_total = get_total_object_values(&objects->type_counts);\n \tstats_table_addf(table, \"\");\n \tstats_table_addf(table, \"* %s\", _(\"Reachable objects\"));\n-\tstats_table_count_addf(table, object_count_total, \"  * %s\", _(\"Count\"));\n+\tstats_table_count_addf(table, object_count_total, CONCERN_STAT_VALUE_NONE,\n+\t\t\t       \"  * %s\", _(\"Count\"));\n \tstats_table_count_addf(table, objects->type_counts.commits,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_COMMIT_COUNT,\n+\t\t\t\t       objects->type_counts.commits,\n+\t\t\t\t       repo_structure_concern_thresholds,\n+\t\t\t\t       show_concern),\n \t\t\t       \"    * %s\", _(\"Commits\"));\n \tstats_table_count_addf(table, objects->type_counts.trees,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_TREE_COUNT,\n+\t\t\t\t       objects->type_counts.trees,\n+\t\t\t\t       repo_structure_concern_thresholds,\n+\t\t\t\t       show_concern),\n \t\t\t       \"    * %s\", _(\"Trees\"));\n \tstats_table_count_addf(table, objects->type_counts.blobs,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_BLOB_COUNT,\n+\t\t\t\t       objects->type_counts.blobs,\n+\t\t\t\t       repo_structure_concern_thresholds,\n+\t\t\t               show_concern),\n \t\t\t       \"    * %s\", _(\"Blobs\"));\n \tstats_table_count_addf(table, objects->type_counts.tags,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_TAG_COUNT,\n+\t\t\t\t       objects->type_counts.tags,\n+\t\t\t\t       repo_structure_concern_thresholds,\n+\t\t\t\t       show_concern),\n \t\t\t       \"    * %s\", _(\"Tags\"));\n \n \tinflated_object_total = get_total_object_values(&objects->inflated_sizes);\n-\tstats_table_size_addf(table, inflated_object_total,\n+\tstats_table_size_addf(table, inflated_object_total, CONCERN_STAT_VALUE_NONE,\n \t\t\t      \"  * %s\", _(\"Inflated size\"));\n \tstats_table_size_addf(table, objects->inflated_sizes.commits,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_COMMIT_INFLATED_SIZE,\n+\t\t\t\t      objects->inflated_sizes.commits,\n+\t\t\t\t      repo_structure_concern_thresholds,\n+\t\t\t\t      show_concern),\n \t\t\t      \"    * %s\", _(\"Commits\"));\n \tstats_table_size_addf(table, objects->inflated_sizes.trees,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_TREE_INFLATED_SIZE,\n+\t\t\t\t      objects->inflated_sizes.trees,\n+\t\t\t\t      repo_structure_concern_thresholds,\n+\t\t\t\t      show_concern),\n \t\t\t      \"    * %s\", _(\"Trees\"));\n \tstats_table_size_addf(table, objects->inflated_sizes.blobs,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_BLOB_INFLATED_SIZE,\n+\t\t\t\t      objects->inflated_sizes.blobs,\n+\t\t\t\t      repo_structure_concern_thresholds,\n+\t\t\t\t      show_concern),\n \t\t\t      \"    * %s\", _(\"Blobs\"));\n \tstats_table_size_addf(table, objects->inflated_sizes.tags,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_TAG_INFLATED_SIZE,\n+\t\t\t\t      objects->inflated_sizes.tags,\n+\t\t\t\t      repo_structure_concern_thresholds,\n+\t\t\t\t      show_concern),\n \t\t\t      \"    * %s\", _(\"Tags\"));\n \n \tdisk_object_total = get_total_object_values(&objects->disk_sizes);\n-\tstats_table_size_addf(table, disk_object_total,\n+\tstats_table_size_addf(table, disk_object_total, CONCERN_STAT_VALUE_NONE,\n \t\t\t      \"  * %s\", _(\"Disk size\"));\n \tstats_table_size_addf(table, objects->disk_sizes.commits,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_COMMIT_SIZE,\n+\t\t\t\t      objects->disk_sizes.commits,\n+\t\t\t\t      repo_structure_concern_thresholds,\n+\t\t\t\t      show_concern),\n \t\t\t      \"    * %s\", _(\"Commits\"));\n \tstats_table_size_addf(table, objects->disk_sizes.trees,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_TREE_SIZE,\n+\t\t\t\t      objects->disk_sizes.trees,\n+\t\t\t\t      repo_structure_concern_thresholds,\n+\t\t\t\t      show_concern),\n \t\t\t      \"    * %s\", _(\"Trees\"));\n \tstats_table_size_addf(table, objects->disk_sizes.blobs,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_BLOB_SIZE,\n+\t\t\t\t      objects->disk_sizes.blobs,\n+\t\t\t\t      repo_structure_concern_thresholds,\n+\t\t\t\t      show_concern),\n \t\t\t      \"    * %s\", _(\"Blobs\"));\n \tstats_table_size_addf(table, objects->disk_sizes.tags,\n+\t\t\t      repo_structure_concern_level(\n+\t\t\t\t      CONCERN_STAT_TAG_SIZE,\n+\t\t\t\t      objects->disk_sizes.tags,\n+\t\t\t\t      repo_structure_concern_thresholds,\n+\t\t\t\t      show_concern),\n \t\t\t      \"    * %s\", _(\"Tags\"));\n \n \tstats_table_addf(table, \"\");\n@@ -550,35 +723,59 @@ static void stats_table_setup_structure(struct stats_table *table,\n \tstats_table_object_size_addf(table,\n \t\t\t\t     &objects->largest.commit_size.oid,\n \t\t\t\t     objects->largest.commit_size.value,\n+\t\t\t\t     repo_structure_concern_level(\n+\t\t\t\t\t     CONCERN_STAT_COMMIT_LARGEST,\n+\t\t\t\t\t     objects->largest.commit_size.value,\n+\t\t\t\t\t     repo_structure_concern_thresholds,\n+\t\t\t\t\t     show_concern),\n \t\t\t\t     \"    * %s\", _(\"Maximum size\"));\n \tstats_table_object_count_addf(table,\n \t\t\t\t      &objects->largest.parent_count.oid,\n \t\t\t\t      objects->largest.parent_count.value,\n+\t\t\t\t      CONCERN_STAT_VALUE_NONE,\n \t\t\t\t      \"    * %s\", _(\"Maximum parents\"));\n \tstats_table_addf(table, \"  * %s\", _(\"Trees\"));\n \tstats_table_object_size_addf(table,\n \t\t\t\t     &objects->largest.tree_size.oid,\n \t\t\t\t     objects->largest.tree_size.value,\n+\t\t\t\t     repo_structure_concern_level(\n+\t\t\t\t\t     CONCERN_STAT_TREE_LARGEST,\n+\t\t\t\t\t     objects->largest.tree_size.value,\n+\t\t\t\t\t     repo_structure_concern_thresholds,\n+\t\t\t\t\t     show_concern),\n \t\t\t\t     \"    * %s\", _(\"Maximum size\"));\n+\n \tstats_table_object_count_addf(table,\n-\t\t\t\t      &objects->largest.tree_entries.oid,\n-\t\t\t\t      objects->largest.tree_entries.value,\n-\t\t\t\t      \"    * %s\", _(\"Maximum entries\"));\n+\t\t\t\t     &objects->largest.tree_entries.oid,\n+\t\t\t\t     objects->largest.tree_entries.value,\n+\t\t\t             CONCERN_STAT_VALUE_NONE,\n+\t\t\t\t     \"    * %s\", _(\"Maximum entries\"));\n \tstats_table_addf(table, \"  * %s\", _(\"Blobs\"));\n \tstats_table_object_size_addf(table,\n \t\t\t\t     &objects->largest.blob_size.oid,\n \t\t\t\t     objects->largest.blob_size.value,\n+\t\t\t\t     repo_structure_concern_level(\n+\t\t\t\t\t     CONCERN_STAT_BLOB_LARGEST,\n+\t\t\t\t\t     objects->largest.blob_size.value,\n+\t\t\t\t\t     repo_structure_concern_thresholds,\n+\t\t\t\t\t     show_concern),\n \t\t\t\t     \"    * %s\", _(\"Maximum size\"));\n \tstats_table_addf(table, \"  * %s\", _(\"Tags\"));\n \tstats_table_object_size_addf(table,\n \t\t\t\t     &objects->largest.tag_size.oid,\n \t\t\t\t     objects->largest.tag_size.value,\n+\t\t\t\t     repo_structure_concern_level(\n+\t\t\t\t\t     CONCERN_STAT_TAG_LARGEST,\n+\t\t\t\t\t     objects->largest.tag_size.value,\n+\t\t\t\t\t     repo_structure_concern_thresholds,\n+\t\t\t\t\t     show_concern),\n \t\t\t\t     \"    * %s\", _(\"Maximum size\"));\n }\n \n #define INDEX_WIDTH 4\n \n-static void stats_table_print_structure(const struct stats_table *table)\n+static void stats_table_print_structure(const struct stats_table *table,\n+\t\t\t\t\tbool show_concern)\n {\n \tconst char *name_col_title = _(\"Repository structure\");\n \tconst char *value_col_title = _(\"Value\");\n@@ -587,14 +784,17 @@ static void stats_table_print_structure(const struct stats_table *table)\n \tint name_col_width = table->name_col_width;\n \tint value_col_width = table->value_col_width;\n \tint unit_col_width = table->unit_col_width;\n+\tconst char *concern_col_title = _(\"Concern\");\n+\tconst size_t concern_title_width = strlen(concern_col_title);\n+\tsize_t concern_col_width = (table->max_concern_value > concern_title_width) ? table->max_concern_value : concern_title_width;\n \tstruct string_list_item *item;\n \tstruct strbuf buf = STRBUF_INIT;\n+\tstruct strbuf concern_str = STRBUF_INIT;\n \n \tif (title_name_width > name_col_width)\n \t\tname_col_width = title_name_width;\n \tif (title_value_width > value_col_width + unit_col_width + 1)\n \t\tvalue_col_width = title_value_width - unit_col_width;\n-\n \tstrbuf_addstr(&buf, \"| \");\n \tstrbuf_utf8_align(&buf, ALIGN_LEFT, name_col_width + INDEX_WIDTH,\n \t\t\t  name_col_title);\n@@ -602,6 +802,12 @@ static void stats_table_print_structure(const struct stats_table *table)\n \tstrbuf_utf8_align(&buf, ALIGN_LEFT,\n \t\t\t  value_col_width + unit_col_width + 1, value_col_title);\n \tstrbuf_addstr(&buf, \" |\");\n+\tif (show_concern) {\n+\t\tstrbuf_addch(&buf, ' ');\n+\t\tstrbuf_utf8_align(&buf, ALIGN_LEFT, concern_col_width,\n+\t\t\t  concern_col_title);\n+\t\tstrbuf_addstr(&buf, \" |\");\n+\t}\n \tprintf(\"%s\\n\", buf.buf);\n \n \tprintf(\"| \");\n@@ -610,17 +816,28 @@ static void stats_table_print_structure(const struct stats_table *table)\n \tprintf(\" | \");\n \tfor (int i = 0; i < value_col_width + unit_col_width + 1; i++)\n \t\tputchar('-');\n-\tprintf(\" |\\n\");\n+\tprintf(\" |\");\n+\tif (show_concern) {\n+\t\tputchar(' ');\n+\t\tfor (size_t i = 0; i < concern_col_width; i++) {\n+\t\t\tputchar('-');\n+\t\t}\n+\t\tprintf(\" |\");\n+\t}\n+\tputchar('\\n');\n \n \tfor_each_string_list_item(item, &table->rows) {\n \t\tstruct stats_table_entry *entry = item->util;\n \t\tconst char *value = \"\";\n \t\tconst char *unit = \"\";\n+\t\tstrbuf_reset(&concern_str);\n \n \t\tif (entry) {\n \t\t\tvalue = entry->value;\n \t\t\tif (entry->unit)\n \t\t\t\tunit = entry->unit;\n+\t\t\tif (entry->concern > 0)\n+\t\t\t\tstrbuf_addchars(&concern_str, '*', entry->concern);\n \t\t}\n \n \t\tstrbuf_reset(&buf);\n@@ -638,6 +855,11 @@ static void stats_table_print_structure(const struct stats_table *table)\n \t\tstrbuf_addch(&buf, ' ');\n \t\tstrbuf_utf8_align(&buf, ALIGN_LEFT, unit_col_width, unit);\n \t\tstrbuf_addstr(&buf, \" |\");\n+\t\tif (show_concern) {\n+\t\t\tstrbuf_addch(&buf, ' ');\n+\t\t\tstrbuf_utf8_align(&buf, ALIGN_LEFT, concern_col_width, concern_str.buf);\n+\t\t\tstrbuf_addstr(&buf, \" |\");\n+\t\t}\n \t\tprintf(\"%s\\n\", buf.buf);\n \t}\n \n@@ -646,7 +868,7 @@ static void stats_table_print_structure(const struct stats_table *table)\n \t\tfor_each_string_list_item(item, &table->annotations)\n \t\t\tprintf(\"%s\\n\", item->string);\n \t}\n-\n+\tstrbuf_release(&concern_str);\n \tstrbuf_release(&buf);\n }\n \n@@ -681,7 +903,8 @@ static void print_object_data(const char *key, char key_delim,\n }\n \n static void structure_keyvalue_print(struct repo_structure *stats,\n-\t\t\t\t     char key_delim, char value_delim)\n+\t\t\t\t     char key_delim, char value_delim,\n+\t\t\t\t     bool show_concern)\n {\n \tprint_keyvalue(\"references.branches.count\", key_delim,\n \t\t       stats->refs.branches, value_delim);\n@@ -691,48 +914,170 @@ static void structure_keyvalue_print(struct repo_structure *stats,\n \t\t       stats->refs.remotes, value_delim);\n \tprint_keyvalue(\"references.others.count\", key_delim,\n \t\t       stats->refs.others, value_delim);\n-\n \tprint_keyvalue(\"objects.commits.count\", key_delim,\n \t\t       stats->objects.type_counts.commits, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.commits.count.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_COMMIT_COUNT,\n+\t\t\t\t       stats->objects.type_counts.commits,\n+\t\t\t\t       repo_structure_concern_thresholds,\n+\t\t\t\t       show_concern),\n+\t\t\t       value_delim);\n \tprint_keyvalue(\"objects.trees.count\", key_delim,\n \t\t       stats->objects.type_counts.trees, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.trees.count.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_TREE_COUNT,\n+\t\t\t\t       stats->objects.type_counts.trees,\n+\t\t\t\t       repo_structure_concern_thresholds,\n+\t\t\t\t       show_concern),\n+\t\t\t       value_delim);\n \tprint_keyvalue(\"objects.blobs.count\", key_delim,\n \t\t       stats->objects.type_counts.blobs, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.blobs.count.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_BLOB_COUNT,\n+\t\t\t\t       stats->objects.type_counts.blobs,\n+\t\t\t\t       repo_structure_concern_thresholds,\n+\t\t\t\t       show_concern),\n+\t\t value_delim);\n \tprint_keyvalue(\"objects.tags.count\", key_delim,\n-\t\t       stats->objects.type_counts.tags, value_delim);\n-\n+\t\tstats->objects.type_counts.tags, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.tags.count.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_TAG_COUNT,\n+\t\t\t\t       stats->objects.type_counts.tags,\n+\t\t\t\t       repo_structure_concern_thresholds,\n+\t\t\t\t       show_concern),\n+\t\t\t       value_delim);\n \tprint_keyvalue(\"objects.commits.inflated_size\", key_delim,\n-\t\t       stats->objects.inflated_sizes.commits, value_delim);\n+\t\tstats->objects.inflated_sizes.commits, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.commits.inflated_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_COMMIT_INFLATED_SIZE,\n+\t\t\t\t       stats->objects.inflated_sizes.commits,\n+\t\t\t\t       repo_structure_concern_thresholds,\n+\t\t\t\t       show_concern),\n+\t\t\t       value_delim);\n \tprint_keyvalue(\"objects.trees.inflated_size\", key_delim,\n-\t\t       stats->objects.inflated_sizes.trees, value_delim);\n+\t\tstats->objects.inflated_sizes.trees, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.trees.inflated_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t\tCONCERN_STAT_TREE_INFLATED_SIZE,\n+\t\t\t\t\tstats->objects.inflated_sizes.trees,\n+\t\t\t\t\trepo_structure_concern_thresholds,\n+\t\t\t\t\tshow_concern),\n+\t\t\t       value_delim);\n \tprint_keyvalue(\"objects.blobs.inflated_size\", key_delim,\n \t\t       stats->objects.inflated_sizes.blobs, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.blobs.inflated_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_BLOB_INFLATED_SIZE,\n+\t\t\t\t       stats->objects.inflated_sizes.blobs,\n+\t\t\t\t       repo_structure_concern_thresholds,\n+\t\t\t\t       show_concern),\n+\t\t\t       value_delim);\n \tprint_keyvalue(\"objects.tags.inflated_size\", key_delim,\n \t\t       stats->objects.inflated_sizes.tags, value_delim);\n-\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.tags.inflated_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_TAG_INFLATED_SIZE,\n+\t\t\t\t       stats->objects.inflated_sizes.tags,\n+\t\t\t\t       repo_structure_concern_thresholds,\n+\t\t\t\t       show_concern),\n+\t\t\t       value_delim);\n \tprint_keyvalue(\"objects.commits.disk_size\", key_delim,\n \t\t       stats->objects.disk_sizes.commits, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.commits.disk_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_COMMIT_SIZE,\n+\t\t\t\t       stats->objects.disk_sizes.commits,\n+\t\t\t\t       repo_structure_concern_thresholds,\n+\t\t\t\t       show_concern),\n+\t\t\t       value_delim);\n \tprint_keyvalue(\"objects.trees.disk_size\", key_delim,\n \t\t       stats->objects.disk_sizes.trees, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.trees.disk_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_TREE_SIZE,\n+\t\t\t\t       stats->objects.disk_sizes.trees,\n+\t\t\t\t       repo_structure_concern_thresholds,\n+\t\t\t\t       show_concern),\n+\t\t\t       value_delim);\n \tprint_keyvalue(\"objects.blobs.disk_size\", key_delim,\n \t\t       stats->objects.disk_sizes.blobs, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.blobs.disk_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_BLOB_SIZE,\n+\t\t\t\t       stats->objects.disk_sizes.blobs,\n+\t\t\t\t       repo_structure_concern_thresholds,\n+\t\t\t\t       show_concern),\n+\t\t\t      value_delim);\n \tprint_keyvalue(\"objects.tags.disk_size\", key_delim,\n \t\t       stats->objects.disk_sizes.tags, value_delim);\n-\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.tags.disk_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_TAG_SIZE,\n+\t\t\t\t       stats->objects.disk_sizes.tags,\n+\t\t\t\t       repo_structure_concern_thresholds,\n+\t\t\t\t       show_concern),\n+\t\t\t      value_delim);\n \tprint_object_data(\"objects.commits.max_size\", key_delim,\n \t\t\t  &stats->objects.largest.commit_size, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.commits.max_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_COMMIT_LARGEST,\n+\t\t\t\t       stats->objects.largest.commit_size.value,\n+\t\t\t\t       repo_structure_concern_thresholds,\n+\t\t\t\t       show_concern),\n+\t\t\t       value_delim);\n \tprint_object_data(\"objects.trees.max_size\", key_delim,\n \t\t\t  &stats->objects.largest.tree_size, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.trees.max_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_TREE_LARGEST,\n+\t\t\t\t       stats->objects.largest.tree_size.value,\n+\t\t\t\t       repo_structure_concern_thresholds,\n+\t\t\t\t       show_concern),\n+\t\t\t       value_delim);\n \tprint_object_data(\"objects.blobs.max_size\", key_delim,\n \t\t\t  &stats->objects.largest.blob_size, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.blobs.max_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_BLOB_LARGEST,\n+\t\t\t\t       stats->objects.largest.blob_size.value,\n+\t\t\t\t       repo_structure_concern_thresholds,\n+\t\t\t\t       show_concern),\n+\t\t\t       value_delim);\n \tprint_object_data(\"objects.tags.max_size\", key_delim,\n-\t\t\t  &stats->objects.largest.tag_size, value_delim);\n-\n+\t\t          &stats->objects.largest.tag_size, value_delim);\n+\tif (show_concern)\n+\t\tprint_keyvalue(\"objects.tags.max_size.concern_level\", key_delim,\n+\t\t\t       repo_structure_concern_level(\n+\t\t\t\t       CONCERN_STAT_TAG_LARGEST,\n+\t\t\t\t       stats->objects.largest.tag_size.value,\n+\t\t\t\t       repo_structure_concern_thresholds,\n+\t\t\t\t       show_concern),\n+\t\t\t       value_delim);\n \tprint_object_data(\"objects.commits.max_parents\", key_delim,\n \t\t\t  &stats->objects.largest.parent_count, value_delim);\n \tprint_object_data(\"objects.trees.max_entries\", key_delim,\n \t\t\t  &stats->objects.largest.tree_entries, value_delim);\n-\n \tfflush(stdout);\n }\n \n@@ -935,6 +1280,8 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,\n \tstruct repo_structure stats = { 0 };\n \tstruct rev_info revs;\n \tint show_progress = -1;\n+\tint show_concern = 0;\n+\n \tstruct option options[] = {\n \t\tOPT_CALLBACK_F(0, \"format\", &format, N_(\"format\"),\n \t\t\t       N_(\"output format\"),\n@@ -944,13 +1291,13 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,\n \t\t\t       PARSE_OPT_NONEG | PARSE_OPT_NOARG,\n \t\t\t       parse_format_cb),\n \t\tOPT_BOOL(0, \"progress\", &show_progress, N_(\"show progress\")),\n+\t\tOPT_BOOL(0, \"concern\", &show_concern, N_(\"show level of concern\")),\n \t\tOPT_END()\n \t};\n \n \targc = parse_options(argc, argv, prefix, options, repo_structure_usage, 0);\n \tif (argc)\n \t\tusage(_(\"too many arguments\"));\n-\n \trepo_init_revisions(repo, &revs, prefix);\n \n \tif (show_progress < 0)\n@@ -961,14 +1308,14 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,\n \n \tswitch (format) {\n \tcase FORMAT_TABLE:\n-\t\tstats_table_setup_structure(&table, &stats);\n-\t\tstats_table_print_structure(&table);\n+\t\tstats_table_setup_structure(&table, &stats, show_concern);\n+\t\tstats_table_print_structure(&table, show_concern);\n \t\tbreak;\n \tcase FORMAT_NEWLINE_TERMINATED:\n-\t\tstructure_keyvalue_print(&stats, '=', '\\n');\n+\t\tstructure_keyvalue_print(&stats, '=', '\\n', show_concern);\n \t\tbreak;\n \tcase FORMAT_NUL_TERMINATED:\n-\t\tstructure_keyvalue_print(&stats, '\\n', '\\0');\n+\t\tstructure_keyvalue_print(&stats, '\\n', '\\0', show_concern);\n \t\tbreak;\n \tdefault:\n \t\tBUG(\"invalid output format\");\ndiff --git a/t/t1901-repo-structure.sh b/t/t1901-repo-structure.sh\nindex 02cc2b594a..9eafff0a36 100755\n--- a/t/t1901-repo-structure.sh\n+++ b/t/t1901-repo-structure.sh\n@@ -65,7 +65,6 @@ test_expect_success 'empty repository' '\n \t\t|   * Tags                  |        |\n \t\t|     * Maximum size        |    0 B |\n \t\tEOF\n-\n \t\tgit repo structure >out 2>err &&\n \n \t\ttest_cmp expect out &&\n\n-- \n2.53.0\n\n\n"},{"id":"557203","messageId":"20261102-b4-repo-structure-concern-v5-2-2d8f37b70b75@fastmail.com","threadId":"66621","inReplyTo":"20261102-b4-repo-structure-concern-v5-0-2d8f37b70b75@fastmail.com","subject":"[PATCH v5 2/2] repo: make concern thresholds configurable","fromName":"Mark C. Chu-Carroll","fromEmail":"markchucarroll@fastmail.com","sentAt":"2026-11-02T16:32:44Z","receivedAt":"2026-11-02T16:32:44Z","isPatch":true,"sender":{"key":"markchucarroll@fastmail.com","avatar":null},"body":"The concern reporting mechanism so far provides a\nmeasure of concern based on heuristic values estimated\nby analyzing a collection of (hopefully) representative\nrepositories. But for different projects/usecases, the\nthresholds at which a value becomes concerning may vary.\n\nProvide a mechanism using git config to allow users to\nconfigure per-repo concern levels for \"git repo structure\"\nconcern reporting.\n\nSigned-off-by: Mark C. Chu-Carroll <markchucarroll@fastmail.com>\n---\n Documentation/config/repo.adoc |  63 +++++++++++++++++++\n Documentation/git-repo.adoc    |  11 ++++\n builtin/repo.c                 |  60 ++++++++++++++++++\n t/t1901-repo-structure.sh      | 137 +++++++++++++++++++++++++++++++++++++++++\n 4 files changed, 271 insertions(+)\n\ndiff --git a/Documentation/config/repo.adoc b/Documentation/config/repo.adoc\nnew file mode 100644\nindex 0000000000..9692f8b71d\n--- /dev/null\n+++ b/Documentation/config/repo.adoc\n@@ -0,0 +1,63 @@\n+`repo.concern.commit-count`::\n+\tSets the value of the concern threshold for commit count.\n+\n+`repo.concern.tree-count`::\n+\tSets the value of the concern threshold for tree count.\n+\n+`repo.concern.blob-count`::\n+\tSets the value of the concern threshold for blob count.\n+\n+`repo.concern.tag-count`::\n+\tSets the value of the concern threshold for tag count.\n+\n+`repo.concern.commit-size`::\n+\tSets the value of the concern threshold for total commit size.\n+\n+`repo.concern.tree-size`::\n+\tSets the value of the concern threshold for total tree size.\n+\n+`repo.concern.blob-size`::\n+\tSets the value of the concern threshold for total blob size.\n+\n+`repo.concern.tag-size`::\n+\tSets the value of the concern threshold for total tag size.\n+\n+`repo.concern.commit-inflated-size`::\n+\tSets the value of the concern threshold for total size\n+\tof all inflated commits.\n+\n+`repo.concern.tree-inflated-size`::\n+\tSets the value of the concern threshold for total size of\n+\tall inflated trees.\n+\n+`repo.concern.blob-inflated-size`::\n+\tSets the value of the concern threshold for total size of\n+\tall inflated blobs.\n+\n+`repo.concern.tag-inflated-size`::\n+\tSets the value of the concern threshold for the total size\n+\tof all inflated tags.\n+\n+`repo.concern.commit-largest`::\n+\tSets the value of the concern threshold for the size of the largest\n+\tcommit.\n+\n+`repo.concern.tree-largest`::\n+\tSets the value of the concern threshold for the size of the\n+\tlargest tree.\n+\n+`repo.concern.blob-largest`::\n+\tSets the value of the concern threshold for the size of the\n+\tlargest blob.\n+\n+`repo.concern.tag-largest`::\n+\tSets the value of the concern threshold for the size of the\n+\tlargest tag.\n+\n+Thresholds are actually computed using log values. But for human convenience,\n+they're set as integer values. If the integer value supplied isn't a power of\n+two, the actual value will be rounded to the nearest power of two. For example,\n+if the value in the configuration setting is 80,000, the actual threshold will\n+be set to 65536; if instead, the value from the configuration was set to\n+100,000, the threshold will be set to 131,072.\n+\ndiff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc\nindex f186b27d87..516403e17d 100644\n--- a/Documentation/git-repo.adoc\n+++ b/Documentation/git-repo.adoc\n@@ -101,6 +101,8 @@ statistic value. The value is considered concerning if it exceeds a heuristicall\n chosen threshold value; the degree of concern is the difference between the\n log_2 of the value, and the log_2 of the corresponding threshold.\n \n+The threshold values are configurable; see the configuration section below.\n+\n INFO KEYS\n ---------\n In order to obtain a set of values from `git repo info`, you should provide\n@@ -153,6 +155,15 @@ using the `nul` format:\n git repo info --format=nul layout.bare layout.shallow\n ------------\n \n+CONFIGURATION\n+-------------\n+\n+include::includes/cmd-config-section-all.adoc[]\n+\n+:git-repo: 1\n+include::config/repo.adoc[]\n+\n+\n SEE ALSO\n --------\n linkgit:git-rev-parse[1]\ndiff --git a/builtin/repo.c b/builtin/repo.c\nindex 7a50605779..5dfe813907 100644\n--- a/builtin/repo.c\n+++ b/builtin/repo.c\n@@ -430,6 +430,8 @@ static int repo_structure_concern_level(const char *concern_name,\n \t\tconst int threshold = get_concern_threshold(concern_name, thresholds);\n \t\tif (threshold < 0)\n \t\t\tBUG(\"unexpected repository stat threshold: '%s'\", concern_name);\n+\t\tif (!threshold)\n+\t\t\treturn 0;\n \t\tlog_value = (size_t)log2u(value);\n \t\tif (log_value > (size_t)threshold)\n \t\t\treturn log_value - threshold;\n@@ -440,6 +442,60 @@ static int repo_structure_concern_level(const char *concern_name,\n \t}\n }\n \n+/*\n+ * To make configuring thresholds as intuitive as possible, we set the\n+ * internal threshold value to the _nearest_ power of two to the value\n+ * the user specified. So if they set a threshold to 500, we should use\n+ * a log value of 9, but log2u would return 8.\n+ *\n+ * A threshold value of 0 means \"never flag concerns for this stat\".\n+ */\n+static size_t nearest_log2_value(size_t v)\n+{\n+\tsize_t logval, power_of_two_floor, mid;\n+\tif (!v)\n+\t\treturn 0;\n+\tlogval = (size_t)log2u(v);\n+\tpower_of_two_floor = (size_t)1 << logval;\n+\tmid = (power_of_two_floor + (power_of_two_floor << 1)) / 2;\n+\treturn (v <= mid) ? logval : (logval + 1);\n+}\n+\n+/*\n+ * Concern thresholds aren't universal. We try to set reasonable default\n+ * values, but projects may want to set these thresholds differently. To\n+ * support that, they can set the thresholds for their project in the\n+ * project's git config, in the section \"repo.concern\".\n+ *\n+ * This is used by repo_config; the call site is guarded by show_concern,\n+ * so the global threshold array is only modified when --concern is requested,\n+ * and that will happen at most once.\n+ */\n+static int config_concern_thresholds(const char *config_val_name,\n+\t\t\t\t     const char *str_val,\n+\t\t\t\t     const struct config_context *ctx,\n+\t\t\t\t     void *cb)\n+{\n+\tstruct concern_stat_threshold *thresholds = cb;\n+\tconst char *concern_name;\n+\tif (skip_prefix(config_val_name, REPO_STRUCTURE_CONFIG_PREFIX, &concern_name)) {\n+\t\tfor (int i = 0; thresholds[i].name; i++) {\n+\t\t\tif (!strcmp(thresholds[i].name, concern_name)) {\n+\t\t\t\tint threshold = git_config_int(concern_name, str_val, ctx->kvi);\n+\t\t\t\tif (threshold < 0) {\n+\t\t\t\t\twarning(_(\"invalid concern threshold '%d': value must be 0 or more\"),\n+\t\t\t\t\t\tthreshold);\n+\t\t\t\t\treturn 0;\n+\t\t\t\t}\n+\t\t\t\tthresholds[i].threshold = nearest_log2_value(threshold);\n+\t\t\t\treturn 0;\n+\t\t\t}\n+\t\t}\n+\t\twarning(_(\"invalid concern threshold name: '%s'\"), concern_name);\n+\t}\n+\treturn 0;\n+}\n+\n /*\n  * Holds column data that gets stored for each row.\n  */\n@@ -1300,6 +1356,10 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,\n \t\tusage(_(\"too many arguments\"));\n \trepo_init_revisions(repo, &revs, prefix);\n \n+\tif (show_concern)\n+\t\trepo_config(repo, &config_concern_thresholds,\n+\t\t\t    &repo_structure_concern_thresholds);\n+\n \tif (show_progress < 0)\n \t\tshow_progress = isatty(2);\n \ndiff --git a/t/t1901-repo-structure.sh b/t/t1901-repo-structure.sh\nindex 9eafff0a36..b43b766e81 100755\n--- a/t/t1901-repo-structure.sh\n+++ b/t/t1901-repo-structure.sh\n@@ -143,6 +143,89 @@ test_expect_success SHA1 'repository with references and objects' '\n \t)\n '\n \n+test_expect_success SHA1 'concern flagging' '\n+\ttest_when_finished \"rm -rf repo\" &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\t# differences between zlib and zlib-ng can cause this\n+\t\t# test to fail; so we just turn it off.\n+\t\tgit config core.compression 0 &&\n+\t\t# configure thresholds to get concern indications\n+\t\tgit config repo.concern.commit-count 120 &&\n+\t\tgit config repo.concern.commit-size 16384 &&\n+\t\tgit config repo.concern.tree-size 1024 &&\n+\t\tgit config repo.concern.blob-size 256 &&\n+\t\tgit config repo.concern.blob-inflated-size 1024 &&\n+\t\tgit config repo.concern.tag-size 4 &&\n+\t\tgit config repo.concern.commit-largest 16 &&\n+\t\tgit config repo.concern.blob-largest 4 &&\n+\t\ttest_commit_bulk 1005 &&\n+\t\tgit tag -a foo -m bar &&\n+\t\toid=\"$(git rev-parse HEAD)\" &&\n+\t\tgit update-ref refs/remotes/origin/foo \"$oid\" &&\n+\n+\t\t# Also creates a commit, tree, and blob.\n+\t\tgit notes add -m foo &&\n+\n+\n+\t\t# The tags disk size is handled specially due to the\n+\t\t# git-rev-list(1) --disk-usage=human option printing the full\n+\t\t# \"byte/bytes\" unit string instead of just \"B\".\n+\t\tcat >expect <<-EOF &&\n+\t\t| Repository structure      | Value      | Concern  |\n+\t\t| ------------------------- | ---------- | -------- |\n+\t\t| * References              |            |          |\n+\t\t|   * Count                 |      4     |          |\n+\t\t|     * Branches            |      1     |          |\n+\t\t|     * Tags                |      1     |          |\n+\t\t|     * Remotes             |      1     |          |\n+\t\t|     * Others              |      1     |          |\n+\t\t|                           |            |          |\n+\t\t| * Reachable objects       |            |          |\n+\t\t|   * Count                 |   3.02 k   |          |\n+\t\t|     * Commits             |   1.01 k   | **       |\n+\t\t|     * Trees               |   1.01 k   |          |\n+\t\t|     * Blobs               |   1.01 k   |          |\n+\t\t|     * Tags                |      1     |          |\n+\t\t|   * Inflated size         |  16.03 MiB |          |\n+\t\t|     * Commits             | 217.92 KiB |          |\n+\t\t|     * Trees               |  15.81 MiB |          |\n+\t\t|     * Blobs               |  11.68 KiB | ***      |\n+\t\t|     * Tags                |    132 B   |          |\n+\t\t|   * Disk size             | $(object_type_disk_usage all true) |          |\n+\t\t|     * Commits             | $(object_type_disk_usage commit true) | ***      |\n+\t\t|     * Trees               | $(object_type_disk_usage tree true) | ******** |\n+\t\t|     * Blobs               |  $(object_type_disk_usage blob true) | ******   |\n+\t\t|     * Tags                |    $(object_type_disk_usage tag) B   | *****    |\n+\t\t|                           |            |          |\n+\t\t| * Largest objects         |            |          |\n+\t\t|   * Commits               |            |          |\n+\t\t|     * Maximum size    [1] |    223 B   | ***      |\n+\t\t|     * Maximum parents [2] |      1     |          |\n+\t\t|   * Trees                 |            |          |\n+\t\t|     * Maximum size    [3] |  32.29 KiB |          |\n+\t\t|     * Maximum entries [4] |   1.01 k   |          |\n+\t\t|   * Blobs                 |            |          |\n+\t\t|     * Maximum size    [5] |     13 B   | *        |\n+\t\t|   * Tags                  |            |          |\n+\t\t|     * Maximum size    [6] |    132 B   |          |\n+\n+\t\t[1] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a\n+\t\t[2] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a\n+\t\t[3] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c\n+\t\t[4] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c\n+\t\t[5] 97d808e45116bf02103490294d3d46dad7a2ac62\n+\t\t[6] 4dae4f5954f5e6feb3577cfb1b181daa3fd3afd2\n+\t\tEOF\n+\n+\t\tgit repo structure --concern >actual 2>err &&\n+\n+\t\ttest_cmp expect actual &&\n+\t\ttest_line_count = 0 err\n+\t)\n+'\n+\n test_expect_success SHA1 'lines and nul format' '\n \ttest_when_finished \"rm -rf repo\" &&\n \tgit init repo &&\n@@ -203,6 +286,60 @@ test_expect_success SHA1 'lines and nul format' '\n \t)\n '\n \n+test_expect_success SHA1 'lines format with concerns' '\n+\ttest_when_finished \"rm -rf repo\" &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\t# configure thresholds to get concern indications\n+\t\tgit config core.compression 0 &&\n+\t\tgit config repo.concern.commit-count 100 &&\n+\t\tgit config repo.concern.commit-size 16384 &&\n+\t\tgit config repo.concern.tree-size 1024 &&\n+\t\tgit config repo.concern.blob-size 256 &&\n+\t\tgit config repo.concern.blob-inflated-size 1024 &&\n+\t\tgit config repo.concern.tag-size 4 &&\n+\t\tgit config repo.concern.commit-largest 16 &&\n+\t\tgit config repo.concern.blob-largest 4 &&\n+\t\ttouch configured &&\n+\t\ttest_commit_bulk 1005 &&\n+\t\tgit tag -a foo -m bar &&\n+\t\toid=\"$(git rev-parse HEAD)\" &&\n+\t\tgit update-ref refs/remotes/origin/foo \"$oid\" &&\n+\n+\t\t# Also creates a commit, tree, and blob.\n+\t\tgit notes add -m foo &&\n+\n+\n+\t\t# The tags disk size is handled specially due to the\n+\t\t# git-rev-list(1) --disk-usage=human option printing the full\n+\t\t# \"byte/bytes\" unit string instead of just \"B\".\n+\t\tcat >expect <<-EOF &&\n+\t\tobjects.commits.count.concern_level=2\n+\t\tobjects.trees.count.concern_level=0\n+\t\tobjects.blobs.count.concern_level=0\n+\t\tobjects.tags.count.concern_level=0\n+\t\tobjects.commits.inflated_size.concern_level=0\n+\t\tobjects.trees.inflated_size.concern_level=0\n+\t\tobjects.blobs.inflated_size.concern_level=3\n+\t\tobjects.tags.inflated_size.concern_level=0\n+\t\tobjects.commits.disk_size.concern_level=3\n+\t\tobjects.trees.disk_size.concern_level=8\n+\t\tobjects.blobs.disk_size.concern_level=6\n+\t\tobjects.tags.disk_size.concern_level=5\n+\t\tobjects.commits.max_size.concern_level=3\n+\t\tobjects.trees.max_size.concern_level=0\n+\t\tobjects.blobs.max_size.concern_level=1\n+\t\tobjects.tags.max_size.concern_level=0\n+\t\tEOF\n+\n+\t\tgit repo structure --concern --format=lines >actual 2>err &&\n+\t\ttest_grep \"concern_level\" actual >actual-concerns &&\n+\t\ttest_cmp expect actual-concerns &&\n+\t\ttest_line_count = 0 err\n+\t)\n+'\n+\n test_expect_success 'progress meter option' '\n \ttest_when_finished \"rm -rf repo\" &&\n \tgit init repo &&\n\n-- \n2.53.0\n\n\n"}]}