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