* [PATCH 0/3] repo: add degree-of-concern reporting to structure
@ 2026-10-21 13:55 Mark C. Chu-Carroll
2026-10-21 13:55 ` [PATCH 1/3] repo: add level of concern reporting Mark C. Chu-Carroll
` (4 more replies)
0 siblings, 5 replies; 15+ messages in thread
From: Mark C. Chu-Carroll @ 2026-10-21 13:55 UTC (permalink / raw)
To: git; +Cc: Mark C. Chu-Carroll
"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.
To select defaults for the thresholds where a value becomes concern,
a (hopefully) representative selection of repositories were analyzed.
Based on empirical exploration of the data, the measurement that seemed
closest to accurately assessing when a repository's structure becomes
concerning was based on the base 2 logarithm of the value. Across all of
the sample repositories, for all of the measurements, the point at which
things become problematic was when a base 2 logarithm of a value was
more than 1 standard deviation from the mean of the log_2 values in
the sample set. 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 the short term, projects
may want to set their own thresholds. So this patchset includes support
for configuring the thresholds on a per-reposiry 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.
* The final commit update the documentation for the command and
configuration options.
Signed-off-by: Mark C. Chu-Carroll <markchucarroll@fastmail.com>
---
Mark C. Chu-Carroll (3):
repo: add level of concern reporting
repo: make concern thresholds configurable
repo: add documentation for level-of-concern.
Documentation/config/repo.adoc | 60 ++++++++
Documentation/git-repo.adoc | 27 +++-
builtin/repo.c | 335 ++++++++++++++++++++++++++++++++++++++---
t/t1901-repo-structure.sh | 134 +++++++++++++++++
4 files changed, 535 insertions(+), 21 deletions(-)
---
base-commit: 3bc0341126508f78f5869cbfc0005e987efdf0c7
change-id: 20261019-b4-repo-structure-concern-f1d6f6326095
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH 1/3] repo: add level of concern reporting
2026-10-21 13:55 [PATCH 0/3] repo: add degree-of-concern reporting to structure Mark C. Chu-Carroll
@ 2026-10-21 13:55 ` Mark C. Chu-Carroll
2026-10-21 13:55 ` [PATCH 2/3] repo: make concern thresholds configurable Mark C. Chu-Carroll
` (3 subsequent siblings)
4 siblings, 0 replies; 15+ messages in thread
From: Mark C. Chu-Carroll @ 2026-10-21 13:55 UTC (permalink / raw)
To: git; +Cc: Mark C. Chu-Carroll
"git repo structure" computes a set of statistics
that are useful for diagnosing performance issues in
a git repository. But for a typical user who hasn't
encountered any issues yet, it can be difficult to
interpret the data - when should a value be considered
concerning?
Add an option to "git repo structure" that adds a "degree
of concern" column to the structure report, which indicates
when a stat values has reached a point where it might be
concerning, and indicate how severe the concern should be.
---
builtin/repo.c | 329 +++++++++++++++++++++++++++++++++++++++++++---
sideband.c | 2 +-
t/t1901-repo-structure.sh | 125 ++++++++----------
3 files changed, 366 insertions(+), 90 deletions(-)
diff --git a/builtin/repo.c b/builtin/repo.c
index 84e012f83f..a450497d4e 100644
--- a/builtin/repo.c
+++ b/builtin/repo.c
@@ -27,7 +27,7 @@
"git repo info --keys [--format=(lines|nul) | -z]"
#define REPO_STRUCTURE_USAGE \
- "git repo structure [--format=(table|lines|nul) | -z]"
+ "git repo structure [--format=(table|lines|nul) | -z] [--[no-]concern]"
static const char *const repo_usage[] = {
REPO_INFO_USAGE,
@@ -348,8 +348,118 @@ struct stats_table {
int name_col_width;
int value_col_width;
int unit_col_width;
+ int concern_col_width;
};
+/*
+ * For computing level of concern about a stat, we need to have some
+ * baseline values. For each of these, we'define a minimum level
+ * of concern, which will appear as 1 star. Then we scale exponentially:
+ * each time that value doubles, we add another star.
+ *
+ * The threshold values for concern are expressed as log_2 values of the size
+ * at which the statistic becomes concerning. The default values for the
+ * threshold were selected based on observed values from a collection of
+ * representative-ish repositories of varying size, by finding the mean log_2
+ * value and standard deviation (sigma) for each statistic over the collection.
+ * The threshold for each metric is then set at mean(log_2) + sigma.
+ */
+struct concern_stat_threshold {
+ const char *name;
+ size_t threshold;
+};
+
+enum concern_stat {
+ CONCERN_STAT_COMMIT_COUNT,
+ CONCERN_STAT_TREE_COUNT,
+ CONCERN_STAT_BLOB_COUNT,
+ CONCERN_STAT_TAG_COUNT,
+ CONCERN_STAT_COMMIT_SIZE,
+ CONCERN_STAT_TREE_SIZE,
+ CONCERN_STAT_BLOB_SIZE,
+ CONCERN_STAT_TAG_SIZE,
+ CONCERN_STAT_COMMIT_LARGEST,
+ CONCERN_STAT_TREE_LARGEST,
+ CONCERN_STAT_BLOB_LARGEST,
+ CONCERN_STAT_TAG_LARGEST,
+ CONCERN_STAT_COMMIT_INFLATED_SIZE,
+ CONCERN_STAT_TREE_INFLATED_SIZE,
+ CONCERN_STAT_BLOB_INFLATED_SIZE,
+ CONCERN_STAT_TAG_INFLATED_SIZE,
+};
+
+struct concern_stat_threshold repo_structure_concern_thresholds[] = {
+ { .name = "commit_count", .threshold = 19 },
+ {.name = "tree_count", .threshold = 21 },
+ { .name = "blob_count", .threshold = 22 },
+ { .name = "tag_count", .threshold = 15 },
+ { .name = "commit_size", .threshold = 28 },
+ { .name = "tree_size", .threshold = 27 },
+ { .name = "blob_size", .threshold = 36 },
+ { .name = "tags_size", .threshold = 18 },
+ { .name = "commit_largest", .threshold = 18 },
+ { .name = "tree_largest", .threshold = 18 },
+ { .name = "blob_largest", .threshold = 26 },
+ { .name = "tag_largest", .threshold = 15 },
+ { .name = "commit_inflated_size", .threshold = 28 },
+ { .name = "tree_inflated_size", .threshold = 32 },
+ { .name = "blob_inflated_size", .threshold = 34 },
+ { .name = "tag_inflated_size", .threshold = 25}
+};
+
+/*
+ * Concern thresholds aren't universal. We try to set reasonable default
+ * values, but projects may want to set these thresholds differently. To
+ * support that, they can set the thresholds for their project in the
+ * project's git config, in the section "repo.structure.concern".
+ */
+static int config_concern_thresholds(const char *prefixed_var,
+ const char *str_val,
+ const struct config_context *ctx,
+ void *cb UNUSED) {
+ const char *config_prefix = "repo.structure.concern.";
+ if (!strncmp(config_prefix, prefixed_var, strlen(config_prefix))) {
+ const char *concern_name = &prefixed_var[strlen(config_prefix)];
+ for (int i = 0; concern_stat_thresholds[i].name != 0; i++) {
+ if (!strcmp(concern_stat_thresholds[i].name, concern_name)) {
+ int value = git_config_int(prefixed_var, str_val, ctx->kvi);
+ if (value < 0)
+ return -1;
+ concern_stat_thresholds[i].threshold = value;
+ return 0;
+ }
+ }
+ warning(_("Invalid concern threshold name: '%s'"), concern_name);
+ return 0;
+ }
+ return 0;
+}
+
+static size_t approx_log2(long value) {
+ int position = 0;
+ if (value <= 0) {
+ return 0;
+ }
+ while (value > 1) {
+ position++;
+ value >>= 1;
+ }
+ return position;
+}
+
+static size_t repo_structure_concern_level(enum concern_stat stat_id,
+ size_t value) {
+ size_t log_value;
+ if (stat_id >= ARRAY_SIZE(concern_stat_thresholds))
+ BUG("unexpected repository stat type");
+ log_value = approx_log2(value);
+ if (log_value > concern_stat_thresholds[stat_id].threshold)
+ return log_value -
+ concern_stat_thresholds[stat_id].threshold;
+ else
+ return 0;
+}
+
/*
* Holds column data that gets stored for each row.
*/
@@ -358,6 +468,7 @@ struct stats_table_entry {
const char *unit;
size_t index;
struct object_id *oid;
+ size_t concern;
};
static void stats_table_vaddf(struct stats_table *table,
@@ -391,6 +502,8 @@ 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->unit) {
int unit_width = utf8_strwidth(entry->unit);
if (unit_width > table->unit_col_width)
@@ -410,13 +523,14 @@ static void stats_table_addf(struct stats_table *table, const char *format, ...)
}
static void stats_table_count_addf(struct stats_table *table, size_t value,
- const char *format, ...)
+ size_t concern, const char *format, ...)
{
struct stats_table_entry *entry;
va_list ap;
CALLOC_ARRAY(entry, 1);
humanise_count(value, &entry->value, &entry->unit);
+ entry->concern = concern;
va_start(ap, format);
stats_table_vaddf(table, entry, format, ap);
@@ -425,13 +539,14 @@ static void stats_table_count_addf(struct stats_table *table, size_t value,
static void stats_table_object_count_addf(struct stats_table *table,
struct object_id *oid, size_t value,
- const char *format, ...)
+ size_t concern, const char *format, ...)
{
struct stats_table_entry *entry;
va_list ap;
CALLOC_ARRAY(entry, 1);
humanise_count(value, &entry->value, &entry->unit);
+ entry->concern = concern;
/*
* A NULL OID should not have a table annotation.
@@ -445,6 +560,7 @@ static void stats_table_object_count_addf(struct stats_table *table,
}
static void stats_table_size_addf(struct stats_table *table, size_t value,
+ size_t concern,
const char *format, ...)
{
struct stats_table_entry *entry;
@@ -452,6 +568,7 @@ static void stats_table_size_addf(struct stats_table *table, size_t value,
CALLOC_ARRAY(entry, 1);
humanise_bytes(value, &entry->value, &entry->unit, HUMANISE_COMPACT);
+ entry->concern = concern;
va_start(ap, format);
stats_table_vaddf(table, entry, format, ap);
@@ -460,6 +577,7 @@ static void stats_table_size_addf(struct stats_table *table, size_t value,
static void stats_table_object_size_addf(struct stats_table *table,
struct object_id *oid, size_t value,
+ size_t concern,
const char *format, ...)
{
struct stats_table_entry *entry;
@@ -467,6 +585,7 @@ static void stats_table_object_size_addf(struct stats_table *table,
CALLOC_ARRAY(entry, 1);
humanise_bytes(value, &entry->value, &entry->unit, HUMANISE_COMPACT);
+ entry->concern = concern;
/*
* A NULL OID should not have a table annotation.
@@ -501,47 +620,92 @@ static void stats_table_setup_structure(struct stats_table *table,
ref_total = get_total_reference_count(refs);
stats_table_addf(table, "* %s", _("References"));
- stats_table_count_addf(table, ref_total, " * %s", _("Count"));
- stats_table_count_addf(table, refs->branches, " * %s", _("Branches"));
- stats_table_count_addf(table, refs->tags, " * %s", _("Tags"));
- stats_table_count_addf(table, refs->remotes, " * %s", _("Remotes"));
- stats_table_count_addf(table, refs->others, " * %s", _("Others"));
+ stats_table_count_addf(table, ref_total, 0,
+ " * %s", _("Count"));
+ stats_table_count_addf(table, refs->branches, 0,
+ " * %s", _("Branches"));
+ stats_table_count_addf(table, refs->tags,
+ repo_structure_concern_level(
+ CONCERN_STAT_TAG_COUNT,
+ refs->tags),
+ " * %s", _("Tags"));
+ stats_table_count_addf(table, refs->remotes, 0,
+ " * %s", _("Remotes"));
+ stats_table_count_addf(table, refs->others, 0,
+ " * %s", _("Others"));
object_count_total = get_total_object_values(&objects->type_counts);
stats_table_addf(table, "");
stats_table_addf(table, "* %s", _("Reachable objects"));
- stats_table_count_addf(table, object_count_total, " * %s", _("Count"));
+ stats_table_count_addf(table, object_count_total, 0,
+ " * %s", _("Count"));
stats_table_count_addf(table, objects->type_counts.commits,
+ repo_structure_concern_level(
+ CONCERN_STAT_COMMIT_COUNT,
+ objects->type_counts.commits),
" * %s", _("Commits"));
stats_table_count_addf(table, objects->type_counts.trees,
+ repo_structure_concern_level(
+ CONCERN_STAT_TREE_COUNT,
+ objects->type_counts.trees),
" * %s", _("Trees"));
stats_table_count_addf(table, objects->type_counts.blobs,
+ repo_structure_concern_level(
+ CONCERN_STAT_BLOB_COUNT,
+ objects->type_counts.blobs),
" * %s", _("Blobs"));
stats_table_count_addf(table, objects->type_counts.tags,
+ repo_structure_concern_level(
+ CONCERN_STAT_TAG_COUNT,
+ objects->type_counts.tags),
" * %s", _("Tags"));
inflated_object_total = get_total_object_values(&objects->inflated_sizes);
- stats_table_size_addf(table, inflated_object_total,
+ stats_table_size_addf(table, inflated_object_total, 0,
" * %s", _("Inflated size"));
stats_table_size_addf(table, objects->inflated_sizes.commits,
+ repo_structure_concern_level(
+ CONCERN_STAT_COMMIT_INFLATED_SIZE,
+ objects->inflated_sizes.commits),
" * %s", _("Commits"));
stats_table_size_addf(table, objects->inflated_sizes.trees,
+ repo_structure_concern_level(
+ CONCERN_STAT_TREE_INFLATED_SIZE,
+ objects->inflated_sizes.trees),
" * %s", _("Trees"));
stats_table_size_addf(table, objects->inflated_sizes.blobs,
+ repo_structure_concern_level(
+ CONCERN_STAT_BLOB_INFLATED_SIZE,
+ objects->inflated_sizes.blobs),
" * %s", _("Blobs"));
stats_table_size_addf(table, objects->inflated_sizes.tags,
+ repo_structure_concern_level(
+ CONCERN_STAT_TAG_INFLATED_SIZE,
+ objects->inflated_sizes.tags),
" * %s", _("Tags"));
disk_object_total = get_total_object_values(&objects->disk_sizes);
- stats_table_size_addf(table, disk_object_total,
+ stats_table_size_addf(table, disk_object_total, 0,
" * %s", _("Disk size"));
stats_table_size_addf(table, objects->disk_sizes.commits,
+ repo_structure_concern_level(
+ CONCERN_STAT_COMMIT_SIZE,
+ objects->disk_sizes.commits),
" * %s", _("Commits"));
stats_table_size_addf(table, objects->disk_sizes.trees,
+ repo_structure_concern_level(
+ CONCERN_STAT_TREE_SIZE,
+ objects->disk_sizes.trees),
" * %s", _("Trees"));
stats_table_size_addf(table, objects->disk_sizes.blobs,
+ repo_structure_concern_level(
+ CONCERN_STAT_BLOB_SIZE,
+ objects->disk_sizes.blobs),
" * %s", _("Blobs"));
stats_table_size_addf(table, objects->disk_sizes.tags,
+ repo_structure_concern_level(
+ CONCERN_STAT_TAG_SIZE,
+ objects->disk_sizes.tags),
" * %s", _("Tags"));
stats_table_addf(table, "");
@@ -550,51 +714,71 @@ static void stats_table_setup_structure(struct stats_table *table,
stats_table_object_size_addf(table,
&objects->largest.commit_size.oid,
objects->largest.commit_size.value,
+ repo_structure_concern_level(
+ CONCERN_STAT_COMMIT_LARGEST,
+ objects->largest.commit_size.value),
" * %s", _("Maximum size"));
stats_table_object_count_addf(table,
&objects->largest.parent_count.oid,
objects->largest.parent_count.value,
+ 0,
" * %s", _("Maximum parents"));
stats_table_addf(table, " * %s", _("Trees"));
stats_table_object_size_addf(table,
&objects->largest.tree_size.oid,
objects->largest.tree_size.value,
+ repo_structure_concern_level(
+ CONCERN_STAT_TREE_LARGEST,
+ objects->largest.tree_size.value),
" * %s", _("Maximum size"));
stats_table_object_count_addf(table,
&objects->largest.tree_entries.oid,
objects->largest.tree_entries.value,
+ 0,
" * %s", _("Maximum entries"));
stats_table_addf(table, " * %s", _("Blobs"));
stats_table_object_size_addf(table,
&objects->largest.blob_size.oid,
objects->largest.blob_size.value,
+ repo_structure_concern_level(
+ CONCERN_STAT_BLOB_LARGEST,
+ objects->largest.blob_size.value),
" * %s", _("Maximum size"));
stats_table_addf(table, " * %s", _("Tags"));
stats_table_object_size_addf(table,
&objects->largest.tag_size.oid,
objects->largest.tag_size.value,
+ repo_structure_concern_level(
+ CONCERN_STAT_TAG_LARGEST,
+ objects->largest.tag_size.value),
" * %s", _("Maximum size"));
}
#define INDEX_WIDTH 4
-static void stats_table_print_structure(const struct stats_table *table)
+static void stats_table_print_structure(const struct stats_table *table,
+ bool show_concern)
{
const char *name_col_title = _("Repository structure");
const char *value_col_title = _("Value");
+ 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);
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;
struct string_list_item *item;
struct strbuf buf = STRBUF_INIT;
+ struct strbuf concern_str = STRBUF_INIT;
if (title_name_width > name_col_width)
name_col_width = title_name_width;
if (title_value_width > value_col_width + unit_col_width + 1)
value_col_width = title_value_width - unit_col_width;
-
+ 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);
@@ -602,6 +786,12 @@ static void stats_table_print_structure(const struct stats_table *table)
strbuf_utf8_align(&buf, ALIGN_LEFT,
value_col_width + unit_col_width + 1, value_col_title);
strbuf_addstr(&buf, " |");
+ if (show_concern) {
+ strbuf_addch(&buf, ' ');
+ strbuf_utf8_align(&buf, ALIGN_LEFT, concern_col_width,
+ concern_col_title);
+ strbuf_addstr(&buf, " |");
+ }
printf("%s\n", buf.buf);
printf("| ");
@@ -610,17 +800,28 @@ static void stats_table_print_structure(const struct stats_table *table)
printf(" | ");
for (int i = 0; i < value_col_width + unit_col_width + 1; i++)
putchar('-');
- printf(" |\n");
+ printf(" |");
+ if (show_concern) {
+ putchar(' ');
+ for (int i = 0; i < concern_col_width; i++) {
+ putchar('-');
+ }
+ printf(" |");
+ }
+ putchar('\n');
for_each_string_list_item(item, &table->rows) {
struct stats_table_entry *entry = item->util;
const char *value = "";
const char *unit = "";
+ strbuf_reset(&concern_str);
if (entry) {
value = entry->value;
if (entry->unit)
unit = entry->unit;
+ if (entry->concern > 0)
+ strbuf_addchars(&concern_str, '*', entry->concern);
}
strbuf_reset(&buf);
@@ -638,6 +839,11 @@ static void stats_table_print_structure(const struct stats_table *table)
strbuf_addch(&buf, ' ');
strbuf_utf8_align(&buf, ALIGN_LEFT, unit_col_width, unit);
strbuf_addstr(&buf, " |");
+ if (show_concern) {
+ strbuf_addch(&buf, ' ');
+ strbuf_utf8_align(&buf, ALIGN_LEFT, concern_col_width, concern_str.buf);
+ strbuf_addstr(&buf, " |");
+ }
printf("%s\n", buf.buf);
}
@@ -681,12 +887,15 @@ static void print_object_data(const char *key, char key_delim,
}
static void structure_keyvalue_print(struct repo_structure *stats,
- char key_delim, char value_delim)
+ char key_delim, char value_delim,
+ bool show_concern)
{
print_keyvalue("references.branches.count", key_delim,
stats->refs.branches, value_delim);
+
print_keyvalue("references.tags.count", key_delim,
stats->refs.tags, value_delim);
+
print_keyvalue("references.remotes.count", key_delim,
stats->refs.remotes, value_delim);
print_keyvalue("references.others.count", key_delim,
@@ -694,39 +903,120 @@ static void structure_keyvalue_print(struct repo_structure *stats,
print_keyvalue("objects.commits.count", key_delim,
stats->objects.type_counts.commits, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.commits.count.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_COMMIT_COUNT,
+ stats->objects.type_counts.commits),
+ value_delim);
print_keyvalue("objects.trees.count", key_delim,
stats->objects.type_counts.trees, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.trees.count.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_TREE_COUNT,
+ stats->objects.type_counts.trees),
+ value_delim);
print_keyvalue("objects.blobs.count", key_delim,
stats->objects.type_counts.blobs, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.blobs.count.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_BLOB_COUNT,
+ stats->objects.type_counts.blobs),
+ value_delim);
print_keyvalue("objects.tags.count", key_delim,
stats->objects.type_counts.tags, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.tags.count.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_TAG_COUNT,
+ stats->objects.type_counts.tags),
+ value_delim);
print_keyvalue("objects.commits.inflated_size", key_delim,
stats->objects.inflated_sizes.commits, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.commits.inflated_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_COMMIT_INFLATED_SIZE,
+ stats->objects.inflated_sizes.commits),
+ value_delim);
print_keyvalue("objects.trees.inflated_size", key_delim,
stats->objects.inflated_sizes.trees, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.trees.inflated_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_TREE_INFLATED_SIZE,
+ stats->objects.inflated_sizes.trees),
+ value_delim);
+
print_keyvalue("objects.blobs.inflated_size", key_delim,
stats->objects.inflated_sizes.blobs, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.blobs.inflated_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_BLOB_INFLATED_SIZE,
+ stats->objects.inflated_sizes.blobs),
+ value_delim);
print_keyvalue("objects.tags.inflated_size", key_delim,
stats->objects.inflated_sizes.tags, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.tags.inflated_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_TAG_INFLATED_SIZE,
+ stats->objects.inflated_sizes.tags),
+ value_delim);
print_keyvalue("objects.commits.disk_size", key_delim,
stats->objects.disk_sizes.commits, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.commits.disk_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_COMMIT_SIZE,
+ stats->objects.disk_sizes.commits),
+ value_delim);
print_keyvalue("objects.trees.disk_size", key_delim,
stats->objects.disk_sizes.trees, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.trees.disk_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_TREE_SIZE,
+ stats->objects.disk_sizes.trees),
+ value_delim);
print_keyvalue("objects.blobs.disk_size", key_delim,
stats->objects.disk_sizes.blobs, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.blobs.blob_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_BLOB_SIZE,
+ stats->objects.disk_sizes.blobs),
+ 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),
+ value_delim);
print_object_data("objects.commits.max_size", key_delim,
&stats->objects.largest.commit_size, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.commits.max_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_COMMIT_LARGEST,
+ stats->objects.largest.commit_size.value),
+ value_delim);
print_object_data("objects.trees.max_size", key_delim,
&stats->objects.largest.tree_size, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.trees.max_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_TREE_LARGEST,
+ stats->objects.largest.tree_size.value),
+ value_delim);
print_object_data("objects.blobs.max_size", key_delim,
&stats->objects.largest.blob_size, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.blobs.max_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_BLOB_LARGEST,
+ stats->objects.largest.blob_size.value),
+ value_delim);
print_object_data("objects.tags.max_size", key_delim,
&stats->objects.largest.tag_size, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.tags.max_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_TAG_LARGEST,
+ stats->objects.largest.tag_size.value),
+ value_delim);
print_object_data("objects.commits.max_parents", key_delim,
&stats->objects.largest.parent_count, value_delim);
@@ -935,6 +1225,8 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
struct repo_structure stats = { 0 };
struct rev_info revs;
int show_progress = -1;
+ int show_concern = 0;
+
struct option options[] = {
OPT_CALLBACK_F(0, "format", &format, N_("format"),
N_("output format"),
@@ -944,6 +1236,7 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
PARSE_OPT_NONEG | PARSE_OPT_NOARG,
parse_format_cb),
OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
+ OPT_BOOL(0, "concern", &show_concern, N_("show level of concern")),
OPT_END()
};
@@ -962,13 +1255,13 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
switch (format) {
case FORMAT_TABLE:
stats_table_setup_structure(&table, &stats);
- stats_table_print_structure(&table);
+ stats_table_print_structure(&table, show_concern);
break;
case FORMAT_NEWLINE_TERMINATED:
- structure_keyvalue_print(&stats, '=', '\n');
+ structure_keyvalue_print(&stats, '=', '\n', show_concern);
break;
case FORMAT_NUL_TERMINATED:
- structure_keyvalue_print(&stats, '\n', '\0');
+ structure_keyvalue_print(&stats, '\n', '\0', show_concern);
break;
default:
BUG("invalid output format");
diff --git a/sideband.c b/sideband.c
index 374187402e..0e75ab2317 100644
--- a/sideband.c
+++ b/sideband.c
@@ -1,4 +1,4 @@
-#define USE_THE_REPOSITORY_VARIABLE
+define USE_THE_REPOSITORY_VARIABLE
#define DISABLE_SIGN_COMPARE_WARNINGS
#include "git-compat-util.h"
diff --git a/t/t1901-repo-structure.sh b/t/t1901-repo-structure.sh
index 02cc2b594a..f1e3fd2a75 100755
--- a/t/t1901-repo-structure.sh
+++ b/t/t1901-repo-structure.sh
@@ -67,77 +67,7 @@ test_expect_success 'empty repository' '
EOF
git repo structure >out 2>err &&
-
- test_cmp expect out &&
- test_line_count = 0 err
- )
-'
-
-test_expect_success SHA1 'repository with references and objects' '
- test_when_finished "rm -rf repo" &&
- git init repo &&
- (
- cd repo &&
- test_commit_bulk 1005 &&
- git tag -a foo -m bar &&
-
- oid="$(git rev-parse HEAD)" &&
- git update-ref refs/remotes/origin/foo "$oid" &&
-
- # Also creates a commit, tree, and blob.
- git notes add -m foo &&
-
- # The tags disk size is handled specially due to the
- # git-rev-list(1) --disk-usage=human option printing the full
- # "byte/bytes" unit string instead of just "B".
- cat >expect <<-EOF &&
- | Repository structure | Value |
- | ------------------------- | ---------- |
- | * References | |
- | * Count | 4 |
- | * Branches | 1 |
- | * Tags | 1 |
- | * Remotes | 1 |
- | * Others | 1 |
- | | |
- | * Reachable objects | |
- | * Count | 3.02 k |
- | * Commits | 1.01 k |
- | * Trees | 1.01 k |
- | * Blobs | 1.01 k |
- | * Tags | 1 |
- | * Inflated size | 16.03 MiB |
- | * Commits | 217.92 KiB |
- | * Trees | 15.81 MiB |
- | * Blobs | 11.68 KiB |
- | * Tags | 132 B |
- | * Disk size | $(object_type_disk_usage all true) |
- | * Commits | $(object_type_disk_usage commit true) |
- | * Trees | $(object_type_disk_usage tree true) |
- | * Blobs | $(object_type_disk_usage blob true) |
- | * Tags | $(object_type_disk_usage tag) B |
- | | |
- | * Largest objects | |
- | * Commits | |
- | * Maximum size [1] | 223 B |
- | * Maximum parents [2] | 1 |
- | * Trees | |
- | * Maximum size [3] | 32.29 KiB |
- | * Maximum entries [4] | 1.01 k |
- | * Blobs | |
- | * Maximum size [5] | 13 B |
- | * Tags | |
- | * Maximum size [6] | 132 B |
-
- [1] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a
- [2] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a
- [3] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c
- [4] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c
- [5] 97d808e45116bf02103490294d3d46dad7a2ac62
- [6] 4dae4f5954f5e6feb3577cfb1b181daa3fd3afd2
- EOF
-
- git repo structure >out 2>err &&
+ cp out /tmp/test1-out &&
test_cmp expect out &&
test_line_count = 0 err
@@ -204,6 +134,59 @@ test_expect_success SHA1 'lines and nul format' '
)
'
+test_expect_success SHA1 'lines format with concerns' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ # configure thresholds to get concern indications
+ git config repo.structure.concern.commit-count 7 &&
+ git config repo.structure.concern.commit-size 14 &&
+ git config repo.structure.concern.tree-size 10 &&
+ git config repo.structure.concern.blob-size 8 &&
+ git config repo.structure.concern.blob-inflated-size 10 &&
+ git config repo.structure.concern.tag-size 2 &&
+ git config repo.structure.concern.commit-largest 4 &&
+ git config repo.structure.concern.blob-largest 2 &&
+ touch configured &&
+ test_commit_bulk 1005 &&
+ git tag -a foo -m bar &&
+ oid="$(git rev-parse HEAD)" &&
+ git update-ref refs/remotes/origin/foo "$oid" &&
+
+ # Also creates a commit, tree, and blob.
+ git notes add -m foo &&
+
+
+ # The tags disk size is handled specially due to the
+ # git-rev-list(1) --disk-usage=human option printing the full
+ # "byte/bytes" unit string instead of just "B".
+ cat >expect <<-EOF &&
+ objects.commits.count.concern_level=2
+ objects.trees.count.concern_level=0
+ objects.blobs.count.concern_level=0
+ objects.tags.count.concern_level=0
+ objects.commits.inflated_size.concern_level=0
+ objects.trees.inflated_size.concern_level=0
+ objects.blobs.inflated_size.concern_level=3
+ objects.tags.inflated_size.concern_level=0
+ objects.commits.disk_size.concern_level=3
+ objects.trees.disk_size.concern_level=8
+ objects.blobs.blob_size.concern_level=6
+ objects.tags.disk_size.concern_level=4
+ objects.commits.max_size.concern_level=3
+ objects.trees.max_size.concern_level=0
+ objects.blobs.max_size.concern_level=1
+ objects.tags.max_size.concern_level=0
+ EOF
+
+ git repo structure --concern --format=lines >actual 2>err &&
+ test_grep "concern_level" actual >actual-concerns &&
+ test_cmp expect actual-concerns &&
+ test_line_count = 0 err
+ )
+'
+
test_expect_success 'progress meter option' '
test_when_finished "rm -rf repo" &&
git init repo &&
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 2/3] repo: make concern thresholds configurable
2026-10-21 13:55 [PATCH 0/3] repo: add degree-of-concern reporting to structure Mark C. Chu-Carroll
2026-10-21 13:55 ` [PATCH 1/3] repo: add level of concern reporting Mark C. Chu-Carroll
@ 2026-10-21 13:55 ` Mark C. Chu-Carroll
2026-10-21 13:55 ` [PATCH 3/3] repo: add documentation for level-of-concern Mark C. Chu-Carroll
` (2 subsequent siblings)
4 siblings, 0 replies; 15+ messages in thread
From: Mark C. Chu-Carroll @ 2026-10-21 13:55 UTC (permalink / raw)
To: git; +Cc: Mark C. Chu-Carroll
The concern reporting mechanism so far provides a
measure of concern based on heuristic values estimated
by analyzing a collection of (hopefully) representative
repositories. But for different projects/usecases, the
thresholds at which a value becomes concerning may vary.
Provide a mechanism using git config to allow users to
configure per-repo concern levels for "git repo structure"
concern reporting.
---
builtin/repo.c | 44 +++++++------
sideband.c | 2 +-
t/t1901-repo-structure.sh | 155 +++++++++++++++++++++++++++++++++++++++++++++-
3 files changed, 178 insertions(+), 23 deletions(-)
diff --git a/builtin/repo.c b/builtin/repo.c
index a450497d4e..aa6efdfd25 100644
--- a/builtin/repo.c
+++ b/builtin/repo.c
@@ -2,6 +2,7 @@
#include "builtin.h"
#include "commit.h"
+#include "config.h"
#include "environment.h"
#include "hash.h"
#include "hex.h"
@@ -386,25 +387,27 @@ enum concern_stat {
CONCERN_STAT_TREE_INFLATED_SIZE,
CONCERN_STAT_BLOB_INFLATED_SIZE,
CONCERN_STAT_TAG_INFLATED_SIZE,
+ CONCERN_STAT_MAXIMUM
};
-struct concern_stat_threshold repo_structure_concern_thresholds[] = {
- { .name = "commit_count", .threshold = 19 },
- {.name = "tree_count", .threshold = 21 },
- { .name = "blob_count", .threshold = 22 },
- { .name = "tag_count", .threshold = 15 },
- { .name = "commit_size", .threshold = 28 },
- { .name = "tree_size", .threshold = 27 },
- { .name = "blob_size", .threshold = 36 },
- { .name = "tags_size", .threshold = 18 },
- { .name = "commit_largest", .threshold = 18 },
- { .name = "tree_largest", .threshold = 18 },
- { .name = "blob_largest", .threshold = 26 },
- { .name = "tag_largest", .threshold = 15 },
- { .name = "commit_inflated_size", .threshold = 28 },
- { .name = "tree_inflated_size", .threshold = 32 },
- { .name = "blob_inflated_size", .threshold = 34 },
- { .name = "tag_inflated_size", .threshold = 25}
+struct concern_stat_threshold concern_stat_thresholds[] = {
+ { .name = "commit-count", .threshold = 19 },
+ {.name = "tree-count", .threshold = 21 },
+ { .name = "blob-count", .threshold = 22 },
+ { .name = "tag-count", .threshold = 15 },
+ { .name = "commit-size", .threshold = 28 },
+ { .name = "tree-size", .threshold = 27 },
+ { .name = "blob-size", .threshold = 36 },
+ { .name = "tag-size", .threshold = 18 },
+ { .name = "commit-largest", .threshold = 18 },
+ { .name = "tree-largest", .threshold = 18 },
+ { .name = "blob-largest", .threshold = 26 },
+ { .name = "tag-largest", .threshold = 15 },
+ { .name = "commit-inflated-size", .threshold = 28 },
+ { .name = "tree-inflated-size", .threshold = 32 },
+ { .name = "blob-inflated-size", .threshold = 34 },
+ { .name = "tag-inflated-size", .threshold = 25},
+ { 0 }
};
/*
@@ -450,7 +453,7 @@ static size_t approx_log2(long value) {
static size_t repo_structure_concern_level(enum concern_stat stat_id,
size_t value) {
size_t log_value;
- if (stat_id >= ARRAY_SIZE(concern_stat_thresholds))
+ if (stat_id >= CONCERN_STAT_MAXIMUM)
BUG("unexpected repository stat type");
log_value = approx_log2(value);
if (log_value > concern_stat_thresholds[stat_id].threshold)
@@ -977,7 +980,7 @@ static void structure_keyvalue_print(struct repo_structure *stats,
print_keyvalue("objects.blobs.disk_size", key_delim,
stats->objects.disk_sizes.blobs, value_delim);
if (show_concern)
- print_keyvalue("objects.blobs.blob_size.concern_level", key_delim,
+ print_keyvalue("objects.blobs.disk_size.concern_level", key_delim,
repo_structure_concern_level(CONCERN_STAT_BLOB_SIZE,
stats->objects.disk_sizes.blobs),
value_delim);
@@ -1243,7 +1246,8 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
argc = parse_options(argc, argv, prefix, options, repo_structure_usage, 0);
if (argc)
usage(_("too many arguments"));
-
+ if (show_concern)
+ repo_config(repo, &config_concern_thresholds, NULL);
repo_init_revisions(repo, &revs, prefix);
if (show_progress < 0)
diff --git a/sideband.c b/sideband.c
index 0e75ab2317..374187402e 100644
--- a/sideband.c
+++ b/sideband.c
@@ -1,4 +1,4 @@
-define USE_THE_REPOSITORY_VARIABLE
+#define USE_THE_REPOSITORY_VARIABLE
#define DISABLE_SIGN_COMPARE_WARNINGS
#include "git-compat-util.h"
diff --git a/t/t1901-repo-structure.sh b/t/t1901-repo-structure.sh
index f1e3fd2a75..49ecc081ad 100755
--- a/t/t1901-repo-structure.sh
+++ b/t/t1901-repo-structure.sh
@@ -67,13 +67,164 @@ 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
)
'
+test_expect_success SHA1 'repository with references and objects' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit_bulk 1005 &&
+ git tag -a foo -m bar &&
+
+ oid="$(git rev-parse HEAD)" &&
+ git update-ref refs/remotes/origin/foo "$oid" &&
+
+ # Also creates a commit, tree, and blob.
+ git notes add -m foo &&
+
+ # The tags disk size is handled specially due to the
+ # git-rev-list(1) --disk-usage=human option printing the full
+ # "byte/bytes" unit string instead of just "B".
+ cat >expect <<-EOF &&
+ | Repository structure | Value |
+ | ------------------------- | ---------- |
+ | * References | |
+ | * Count | 4 |
+ | * Branches | 1 |
+ | * Tags | 1 |
+ | * Remotes | 1 |
+ | * Others | 1 |
+ | | |
+ | * Reachable objects | |
+ | * Count | 3.02 k |
+ | * Commits | 1.01 k |
+ | * Trees | 1.01 k |
+ | * Blobs | 1.01 k |
+ | * Tags | 1 |
+ | * Inflated size | 16.03 MiB |
+ | * Commits | 217.92 KiB |
+ | * Trees | 15.81 MiB |
+ | * Blobs | 11.68 KiB |
+ | * Tags | 132 B |
+ | * Disk size | $(object_type_disk_usage all true) |
+ | * Commits | $(object_type_disk_usage commit true) |
+ | * Trees | $(object_type_disk_usage tree true) |
+ | * Blobs | $(object_type_disk_usage blob true) |
+ | * Tags | $(object_type_disk_usage tag) B |
+ | | |
+ | * Largest objects | |
+ | * Commits | |
+ | * Maximum size [1] | 223 B |
+ | * Maximum parents [2] | 1 |
+ | * Trees | |
+ | * Maximum size [3] | 32.29 KiB |
+ | * Maximum entries [4] | 1.01 k |
+ | * Blobs | |
+ | * Maximum size [5] | 13 B |
+ | * Tags | |
+ | * Maximum size [6] | 132 B |
+
+ [1] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a
+ [2] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a
+ [3] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c
+ [4] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c
+ [5] 97d808e45116bf02103490294d3d46dad7a2ac62
+ [6] 4dae4f5954f5e6feb3577cfb1b181daa3fd3afd2
+ EOF
+
+ git repo structure >out 2>err &&
+
+ test_cmp expect out &&
+ test_line_count = 0 err
+ )
+'
+
+test_expect_success SHA1 'concern flagging' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ # configure thresholds to get concern indications
+ git config repo.structure.concern.commit-count 7 &&
+ git config repo.structure.concern.commit-size 14 &&
+ git config repo.structure.concern.tree-size 10 &&
+ git config repo.structure.concern.blob-size 8 &&
+ git config repo.structure.concern.blob-inflated-size 10 &&
+ git config repo.structure.concern.tag-size 2 &&
+ git config repo.structure.concern.commit-largest 4 &&
+ git config repo.structure.concern.blob-largest 2 &&
+ touch configured &&
+ test_commit_bulk 1005 &&
+ git tag -a foo -m bar &&
+ oid="$(git rev-parse HEAD)" &&
+ git update-ref refs/remotes/origin/foo "$oid" &&
+
+ # Also creates a commit, tree, and blob.
+ git notes add -m foo &&
+
+
+ # The tags disk size is handled specially due to the
+ # git-rev-list(1) --disk-usage=human option printing the full
+ # "byte/bytes" unit string instead of just "B".
+ cat >expect <<-EOF &&
+ | Repository structure | Value | Concern |
+ | ------------------------- | ---------- | -------- |
+ | * References | | |
+ | * Count | 4 | |
+ | * Branches | 1 | |
+ | * Tags | 1 | |
+ | * Remotes | 1 | |
+ | * Others | 1 | |
+ | | | |
+ | * Reachable objects | | |
+ | * Count | 3.02 k | |
+ | * Commits | 1.01 k | ** |
+ | * Trees | 1.01 k | |
+ | * Blobs | 1.01 k | |
+ | * Tags | 1 | |
+ | * Inflated size | 16.03 MiB | |
+ | * Commits | 217.92 KiB | |
+ | * Trees | 15.81 MiB | |
+ | * Blobs | 11.68 KiB | *** |
+ | * Tags | 132 B | |
+ | * Disk size | $(object_type_disk_usage all true) | |
+ | * Commits | $(object_type_disk_usage commit true) | *** |
+ | * Trees | $(object_type_disk_usage tree true) | ******** |
+ | * Blobs | $(object_type_disk_usage blob true) | ****** |
+ | * Tags | $(object_type_disk_usage tag) B | **** |
+ | | | |
+ | * Largest objects | | |
+ | * Commits | | |
+ | * Maximum size [1] | 223 B | *** |
+ | * Maximum parents [2] | 1 | |
+ | * Trees | | |
+ | * Maximum size [3] | 32.29 KiB | |
+ | * Maximum entries [4] | 1.01 k | |
+ | * Blobs | | |
+ | * Maximum size [5] | 13 B | * |
+ | * Tags | | |
+ | * Maximum size [6] | 132 B | |
+
+ [1] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a
+ [2] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a
+ [3] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c
+ [4] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c
+ [5] 97d808e45116bf02103490294d3d46dad7a2ac62
+ [6] 4dae4f5954f5e6feb3577cfb1b181daa3fd3afd2
+ EOF
+
+ git repo structure --concern >actual 2>err &&
+
+ test_cmp expect actual &&
+ test_line_count = 0 err
+ )
+'
+
test_expect_success SHA1 'lines and nul format' '
test_when_finished "rm -rf repo" &&
git init repo &&
@@ -172,7 +323,7 @@ test_expect_success SHA1 'lines format with concerns' '
objects.tags.inflated_size.concern_level=0
objects.commits.disk_size.concern_level=3
objects.trees.disk_size.concern_level=8
- objects.blobs.blob_size.concern_level=6
+ objects.blobs.disk_size.concern_level=6
objects.tags.disk_size.concern_level=4
objects.commits.max_size.concern_level=3
objects.trees.max_size.concern_level=0
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 3/3] repo: add documentation for level-of-concern.
2026-10-21 13:55 [PATCH 0/3] repo: add degree-of-concern reporting to structure Mark C. Chu-Carroll
2026-10-21 13:55 ` [PATCH 1/3] repo: add level of concern reporting Mark C. Chu-Carroll
2026-10-21 13:55 ` [PATCH 2/3] repo: make concern thresholds configurable Mark C. Chu-Carroll
@ 2026-10-21 13:55 ` Mark C. Chu-Carroll
2026-10-26 17:43 ` [PATCH v2 0/3] repo: add degree-of-concern reporting to structure Mark C. Chu-Carroll
2026-10-27 15:39 ` [PATCH v3 0/2] repo: add degree-of-concern reporting to structure Mark C. Chu-Carroll
4 siblings, 0 replies; 15+ messages in thread
From: Mark C. Chu-Carroll @ 2026-10-21 13:55 UTC (permalink / raw)
To: git; +Cc: Mark C. Chu-Carroll
---
Documentation/config/repo.adoc | 60 ++++++++++++++++++++++++++++++++++++++++++
Documentation/git-repo.adoc | 27 +++++++++++++++++--
2 files changed, 85 insertions(+), 2 deletions(-)
diff --git a/Documentation/config/repo.adoc b/Documentation/config/repo.adoc
new file mode 100644
index 0000000000..ad16bdcc67
--- /dev/null
+++ b/Documentation/config/repo.adoc
@@ -0,0 +1,60 @@
+`repo.structure.concern.commit-count`::
+ Sets the value of the concern threshold for commit count.
+
+`repo.structure.concern.tree-count`::
+ Sets the value of the concern threshold for tree count.
+
+`repo.structure.concern.blob-count`::
+ Sets the value of the concern threshold for blob count.
+
+`repo.structure.concern.tag-count`::
+ Sets the value of the concern threshold for tag count.
+
+`repo.structure.concern.commit-size`::
+ Sets the value of the concern threshold for total commit size.
+
+`repo.structure.concern.tree-size`::
+ Sets the value of the concern threshold for total tree size.
+
+`repo.structure.concern.blob-size`::
+ Sets the value of the concern threshold for total blob size.
+
+`repo.structure.concern.tag-size`::
+ Sets the value of the concern threshold for total tag size.
+
+`repo.structure.concern.commit-inflated-size`::
+ Sets the value of the concern threshold for total size
+ of all inflated commits.
+
+`repo.structure.concern.tree-inflated-size`::
+ Sets the value of the concern threshold for total size of
+ all inflated trees.
+
+`repo.structure.concern.blob-inflated-size`::
+ Sets the value of the concern threshold for total size of
+ all inflated blobs.
+
+`repo.structure.concern.tag-inflated-size`::
+ Sets the value of the concern threshold for the total size
+ of all inflated tags.
+
+`repo.structure.concern.commit-largest`::
+ Sets the value of the concern threshold the size of the largest
+ commit.
+
+`repo.structure.concern.tree-largest`::
+ Sets the value of the concern threshold for the size of the
+ largest tree.
+
+`repo.structure.concern.blob-largest`::
+ Sets the value of the concern threshold for the size of the
+ largest blob.
+
+`repo.structure.concern.tag-largest`::
+ 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.
+
diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc
index ed7d80c690..3c1045eb40 100644
--- a/Documentation/git-repo.adoc
+++ b/Documentation/git-repo.adoc
@@ -10,7 +10,7 @@ SYNOPSIS
[synopsis]
git repo info [--format=(lines|nul) | -z] [--all | <key>...]
git repo info --keys [--format=(lines|nul) | -z]
-git repo structure [--format=(table|lines|nul) | -z]
+git repo structure [--format=(table|lines|nul) | -z] [--[no-]concern]
DESCRIPTION
-----------
@@ -56,7 +56,7 @@ supported:
`nul`:::
Similar to `lines`, but using a _NUL_ character after each value.
-`structure [--format=(table|lines|nul) | -z]`::
+`structure [--format=(table|lines|nul) | -z] [--[no-]concern]`::
Retrieve statistics about the current repository structure. The
following kinds of information are reported:
+
@@ -88,6 +88,20 @@ supported:
quoted.
+
`-z` is an alias for `--format=nul`.
++
+To make it easier to know when one of the values is something that
+users should be concerned about, adding '--concern' will provide a
+level of concern to be added to the output. In 'table' mode, this will
+be a table column indicating the level of concern as a number of stars;
+in 'lines` or `nul' mode, a `concern_level` line will be added for each
+statistic with a numeric value.
++
+The level of corcern for each statistic is computed using the base_2 log of the
+statistic value. The value is considered concerning if it exceeds a heuristically
+chosen threshold value; the degree of concern is the difference between the
+base_2 log of the value, and the base_2 log of the corresponding threshold.
++
+The threshold values are configurable; see the configuration section below.
INFO KEYS
---------
@@ -141,6 +155,15 @@ using the `nul` format:
git repo info --format=nul layout.bare layout.shallow
------------
+CONFIGURATION
+-------------
+
+include::includes/cmd-config-section-all.adoc[]
+
+:git-repo: 1
+include::config/repo.adoc[]
+
+
SEE ALSO
--------
linkgit:git-rev-parse[1]
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v2 0/3] repo: add degree-of-concern reporting to structure
2026-10-21 13:55 [PATCH 0/3] repo: add degree-of-concern reporting to structure Mark C. Chu-Carroll
` (2 preceding siblings ...)
2026-10-21 13:55 ` [PATCH 3/3] repo: add documentation for level-of-concern Mark C. Chu-Carroll
@ 2026-10-26 17:43 ` Mark C. Chu-Carroll
2026-10-26 17:43 ` [PATCH v2 1/3] repo: add level of concern reporting Mark C. Chu-Carroll
` (2 more replies)
2026-10-27 15:39 ` [PATCH v3 0/2] repo: add degree-of-concern reporting to structure Mark C. Chu-Carroll
4 siblings, 3 replies; 15+ messages in thread
From: Mark C. Chu-Carroll @ 2026-10-26 17:43 UTC (permalink / raw)
To: git; +Cc: Mark C. Chu-Carroll
"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.
To select defaults for the thresholds where a value becomes concern,
a (hopefully) representative selection of repositories were analyzed.
Based on empirical exploration of the data, the measurement that seemed
closest to accurately assessing when a repository's structure becomes
concerning was based on the base 2 logarithm of the value. Across all of
the sample repositories, for all of the measurements, the point at which
things become problematic was when a base 2 logarithm of a value was
more than 1 standard deviation from the mean of the log_2 values in
the sample set. 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 the short term, projects
may want to set their own thresholds. So this patchset includes support
for configuring the thresholds on a per-reposiry 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.
* The final commit update the documentation for the command and
configuration options.
Signed-off-by: Mark C. Chu-Carroll <markchucarroll@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
---
Mark C. Chu-Carroll (3):
repo: add level of concern reporting
repo: make concern thresholds configurable
repo: add documentation for level-of-concern.
Documentation/config/repo.adoc | 59 +++++++
Documentation/git-repo.adoc | 27 +++-
builtin/repo.c | 343 ++++++++++++++++++++++++++++++++++++++---
t/t1901-repo-structure.sh | 137 ++++++++++++++++
4 files changed, 543 insertions(+), 23 deletions(-)
Range-diff versus v1:
1: c2b2a79967 = 1: e8971a4050 repo: add level of concern reporting
2: 67d77cfcd9 ! 2: 7a78eee517 repo: make concern thresholds configurable
@@ builtin/repo.c
#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: enum concern_stat {
CONCERN_STAT_TREE_INFLATED_SIZE,
CONCERN_STAT_BLOB_INFLATED_SIZE,
@@ builtin/repo.c: enum concern_stat {
- { .name = "tag_inflated_size", .threshold = 25}
+struct concern_stat_threshold concern_stat_thresholds[] = {
+ { .name = "commit-count", .threshold = 19 },
-+ {.name = "tree-count", .threshold = 21 },
++ { .name = "tree-count", .threshold = 21 },
+ { .name = "blob-count", .threshold = 22 },
+ { .name = "tag-count", .threshold = 15 },
+ { .name = "commit-size", .threshold = 28 },
@@ builtin/repo.c: enum concern_stat {
};
/*
+ * Concern thresholds aren't universal. We try to set reasonable default
+ * values, but projects may want to set these thresholds differently. To
+ * support that, they can set the thresholds for their project in the
+- * project's git config, in the section "repo.structure.concern".
++ * project's git config, in the section "repo.concern".
+ */
+ static int config_concern_thresholds(const char *prefixed_var,
+ const char *str_val,
+ const struct config_context *ctx,
+ void *cb UNUSED) {
+- const char *config_prefix = "repo.structure.concern.";
+- if (!strncmp(config_prefix, prefixed_var, strlen(config_prefix))) {
+- const char *concern_name = &prefixed_var[strlen(config_prefix)];
++ if (!strncmp(REPO_STRUCTURE_CONFIG_PREFIX, prefixed_var,
++ strlen(REPO_STRUCTURE_CONFIG_PREFIX))) {
++ const char *concern_name =
++ &prefixed_var[strlen(REPO_STRUCTURE_CONFIG_PREFIX)];
+ for (int i = 0; concern_stat_thresholds[i].name != 0; i++) {
+ if (!strcmp(concern_stat_thresholds[i].name, concern_name)) {
+ int value = git_config_int(prefixed_var, str_val, ctx->kvi);
+@@ builtin/repo.c: static int config_concern_thresholds(const char *prefixed_var,
+ return 0;
+ }
+
+-static size_t approx_log2(long value) {
++static size_t approx_log2(size_t value) {
+ int position = 0;
+ if (value <= 0) {
+ return 0;
@@ builtin/repo.c: static size_t approx_log2(long value) {
static size_t repo_structure_concern_level(enum concern_stat stat_id,
size_t value) {
@@ builtin/repo.c: static size_t approx_log2(long value) {
BUG("unexpected repository stat type");
log_value = approx_log2(value);
if (log_value > concern_stat_thresholds[stat_id].threshold)
+@@ 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,
print_keyvalue("objects.blobs.disk_size", key_delim,
stats->objects.disk_sizes.blobs, value_delim);
@@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stat
repo_structure_concern_level(CONCERN_STAT_BLOB_SIZE,
stats->objects.disk_sizes.blobs),
value_delim);
+@@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stats,
+ stats->objects.largest.commit_size.value),
+ value_delim);
+ print_object_data("objects.trees.max_size", key_delim,
+- &stats->objects.largest.tree_size, value_delim);
++ &stats->objects.largest.tree_size, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.trees.max_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_TREE_LARGEST,
+@@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stats,
+ stats->objects.largest.blob_size.value),
+ value_delim);
+ print_object_data("objects.tags.max_size", key_delim,
+- &stats->objects.largest.tag_size, value_delim);
++ &stats->objects.largest.tag_size, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.tags.max_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_TAG_LARGEST,
@@ builtin/repo.c: static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
argc = parse_options(argc, argv, prefix, options, repo_structure_usage, 0);
if (argc)
@@ t/t1901-repo-structure.sh: test_expect_success 'empty repository' '
+ git init repo &&
+ (
+ cd repo &&
++ # differences between zlib and zlib-ng can cause this
++ # test to fail; so we just turn it off.
++ git config core.compression 0 &&
+ # configure thresholds to get concern indications
-+ git config repo.structure.concern.commit-count 7 &&
-+ git config repo.structure.concern.commit-size 14 &&
-+ git config repo.structure.concern.tree-size 10 &&
-+ git config repo.structure.concern.blob-size 8 &&
-+ git config repo.structure.concern.blob-inflated-size 10 &&
-+ git config repo.structure.concern.tag-size 2 &&
-+ git config repo.structure.concern.commit-largest 4 &&
-+ git config repo.structure.concern.blob-largest 2 &&
-+ touch configured &&
++ git config repo.concern.commit-count 7 &&
++ git config repo.concern.commit-size 14 &&
++ git config repo.concern.tree-size 10 &&
++ 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 &&
+ test_commit_bulk 1005 &&
+ git tag -a foo -m bar &&
+ oid="$(git rev-parse HEAD)" &&
@@ t/t1901-repo-structure.sh: test_expect_success 'empty repository' '
+ | * Commits | $(object_type_disk_usage commit true) | *** |
+ | * Trees | $(object_type_disk_usage tree true) | ******** |
+ | * Blobs | $(object_type_disk_usage blob true) | ****** |
-+ | * Tags | $(object_type_disk_usage tag) B | **** |
++ | * Tags | $(object_type_disk_usage tag) B | ***** |
+ | | | |
+ | * Largest objects | | |
+ | * Commits | | |
@@ t/t1901-repo-structure.sh: test_expect_success 'empty repository' '
test_expect_success SHA1 'lines and nul format' '
test_when_finished "rm -rf repo" &&
git init repo &&
+@@ t/t1901-repo-structure.sh: test_expect_success SHA1 'lines format with concerns' '
+ git init repo &&
+ (
+ cd repo &&
++ # differences between zlib and zlib-ng can cause this
++ # test to fail; so we just turn it off.
++ git config core.compression 0 &&
+ # configure thresholds to get concern indications
+- git config repo.structure.concern.commit-count 7 &&
+- git config repo.structure.concern.commit-size 14 &&
+- git config repo.structure.concern.tree-size 10 &&
+- git config repo.structure.concern.blob-size 8 &&
+- git config repo.structure.concern.blob-inflated-size 10 &&
+- git config repo.structure.concern.tag-size 2 &&
+- git config repo.structure.concern.commit-largest 4 &&
+- git config repo.structure.concern.blob-largest 2 &&
+- touch configured &&
++ git config repo.concern.commit-count 7 &&
++ git config repo.concern.commit-size 14 &&
++ git config repo.concern.tree-size 10 &&
++ 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 &&
+ test_commit_bulk 1005 &&
+ git tag -a foo -m bar &&
+ oid="$(git rev-parse HEAD)" &&
+@@ t/t1901-repo-structure.sh: test_expect_success SHA1 'lines format with concerns' '
+ # Also creates a commit, tree, and blob.
+ git notes add -m foo &&
+
+-
+ # The tags disk size is handled specially due to the
+ # git-rev-list(1) --disk-usage=human option printing the full
+ # "byte/bytes" unit string instead of just "B".
@@ t/t1901-repo-structure.sh: test_expect_success SHA1 'lines format with concerns' '
objects.tags.inflated_size.concern_level=0
objects.commits.disk_size.concern_level=3
objects.trees.disk_size.concern_level=8
- objects.blobs.blob_size.concern_level=6
+- objects.tags.disk_size.concern_level=4
+ objects.blobs.disk_size.concern_level=6
- objects.tags.disk_size.concern_level=4
++ objects.tags.disk_size.concern_level=5
objects.commits.max_size.concern_level=3
objects.trees.max_size.concern_level=0
+ objects.blobs.max_size.concern_level=1
3: ac0adb7c9f ! 3: cc78eaeca0 repo: add documentation for level-of-concern.
@@ Commit message
## Documentation/config/repo.adoc (new) ##
@@
-+`repo.structure.concern.commit-count`::
++`repo.concern.commit-count`::
+ Sets the value of the concern threshold for commit count.
+
-+`repo.structure.concern.tree-count`::
++`repo.concern.tree-count`::
+ Sets the value of the concern threshold for tree count.
+
-+`repo.structure.concern.blob-count`::
++`repo.concern.blob-count`::
+ Sets the value of the concern threshold for blob count.
+
-+`repo.structure.concern.tag-count`::
++`repo.concern.tag-count`::
+ Sets the value of the concern threshold for tag count.
+
-+`repo.structure.concern.commit-size`::
++`repo.concern.commit-size`::
+ Sets the value of the concern threshold for total commit size.
+
-+`repo.structure.concern.tree-size`::
++`repo.concern.tree-size`::
+ Sets the value of the concern threshold for total tree size.
+
-+`repo.structure.concern.blob-size`::
++`repo.concern.blob-size`::
+ Sets the value of the concern threshold for total blob size.
+
-+`repo.structure.concern.tag-size`::
++`repo.concern.tag-size`::
+ Sets the value of the concern threshold for total tag size.
+
-+`repo.structure.concern.commit-inflated-size`::
++`repo.concern.commit-inflated-size`::
+ Sets the value of the concern threshold for total size
+ of all inflated commits.
+
-+`repo.structure.concern.tree-inflated-size`::
++`repo.concern.tree-inflated-size`::
+ Sets the value of the concern threshold for total size of
+ all inflated trees.
+
-+`repo.structure.concern.blob-inflated-size`::
++`repo.concern.blob-inflated-size`::
+ Sets the value of the concern threshold for total size of
+ all inflated blobs.
+
-+`repo.structure.concern.tag-inflated-size`::
++`repo.concern.tag-inflated-size`::
+ Sets the value of the concern threshold for the total size
+ of all inflated tags.
+
-+`repo.structure.concern.commit-largest`::
++`repo.concern.commit-largest`::
+ Sets the value of the concern threshold the size of the largest
+ commit.
+
-+`repo.structure.concern.tree-largest`::
++`repo.concern.tree-largest`::
+ Sets the value of the concern threshold for the size of the
+ largest tree.
+
-+`repo.structure.concern.blob-largest`::
++`repo.concern.blob-largest`::
+ Sets the value of the concern threshold for the size of the
+ largest blob.
+
-+`repo.structure.concern.tag-largest`::
++`repo.concern.tag-largest`::
+ Sets the value of the concern threshold for the size of the
+ largest tag.
-++
-+All of the concern thresholds are integer values for the
++
++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.
-+
## Documentation/git-repo.adoc ##
@@ Documentation/git-repo.adoc: SYNOPSIS
@@ Documentation/git-repo.adoc: supported:
+users should be concerned about, adding '--concern' will provide a
+level of concern to be added to the output. In 'table' mode, this will
+be a table column indicating the level of concern as a number of stars;
-+in 'lines` or `nul' mode, a `concern_level` line will be added for each
++in `lines` or `nul` mode, a `concern_level` line will be added for each
+statistic with a numeric value.
++
-+The level of corcern for each statistic is computed using the base_2 log of the
++The level of concern for each statistic is computed using the log_2 of the
+statistic value. The value is considered concerning if it exceeds a heuristically
+chosen threshold value; the degree of concern is the difference between the
-+base_2 log of the value, and the base_2 log of the corresponding threshold.
++log_2 of the value, and the log_2 of the corresponding threshold.
++
+The threshold values are configurable; see the configuration section below.
---
base-commit: 3bc0341126508f78f5869cbfc0005e987efdf0c7
change-id: 20261019-b4-repo-structure-concern-f1d6f6326095
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v2 1/3] repo: add level of concern reporting
2026-10-26 17:43 ` [PATCH v2 0/3] repo: add degree-of-concern reporting to structure Mark C. Chu-Carroll
@ 2026-10-26 17:43 ` Mark C. Chu-Carroll
2026-10-27 14:55 ` Patrick Steinhardt
2026-10-26 17:43 ` [PATCH v2 2/3] repo: make concern thresholds configurable Mark C. Chu-Carroll
2026-10-26 17:43 ` [PATCH v2 3/3] repo: add documentation for level-of-concern Mark C. Chu-Carroll
2 siblings, 1 reply; 15+ messages in thread
From: Mark C. Chu-Carroll @ 2026-10-26 17:43 UTC (permalink / raw)
To: git; +Cc: Mark C. Chu-Carroll
"git repo structure" computes a set of statistics
that are useful for diagnosing performance issues in
a git repository. But for a typical user who hasn't
encountered any issues yet, it can be difficult to
interpret the data - when should a value be considered
concerning?
Add an option to "git repo structure" that adds a "degree
of concern" column to the structure report, which indicates
when a stat values has reached a point where it might be
concerning, and indicate how severe the concern should be.
---
builtin/repo.c | 329 +++++++++++++++++++++++++++++++++++++++++++---
sideband.c | 2 +-
t/t1901-repo-structure.sh | 125 ++++++++----------
3 files changed, 366 insertions(+), 90 deletions(-)
diff --git a/builtin/repo.c b/builtin/repo.c
index 84e012f83f..a450497d4e 100644
--- a/builtin/repo.c
+++ b/builtin/repo.c
@@ -27,7 +27,7 @@
"git repo info --keys [--format=(lines|nul) | -z]"
#define REPO_STRUCTURE_USAGE \
- "git repo structure [--format=(table|lines|nul) | -z]"
+ "git repo structure [--format=(table|lines|nul) | -z] [--[no-]concern]"
static const char *const repo_usage[] = {
REPO_INFO_USAGE,
@@ -348,8 +348,118 @@ struct stats_table {
int name_col_width;
int value_col_width;
int unit_col_width;
+ int concern_col_width;
};
+/*
+ * For computing level of concern about a stat, we need to have some
+ * baseline values. For each of these, we'define a minimum level
+ * of concern, which will appear as 1 star. Then we scale exponentially:
+ * each time that value doubles, we add another star.
+ *
+ * The threshold values for concern are expressed as log_2 values of the size
+ * at which the statistic becomes concerning. The default values for the
+ * threshold were selected based on observed values from a collection of
+ * representative-ish repositories of varying size, by finding the mean log_2
+ * value and standard deviation (sigma) for each statistic over the collection.
+ * The threshold for each metric is then set at mean(log_2) + sigma.
+ */
+struct concern_stat_threshold {
+ const char *name;
+ size_t threshold;
+};
+
+enum concern_stat {
+ CONCERN_STAT_COMMIT_COUNT,
+ CONCERN_STAT_TREE_COUNT,
+ CONCERN_STAT_BLOB_COUNT,
+ CONCERN_STAT_TAG_COUNT,
+ CONCERN_STAT_COMMIT_SIZE,
+ CONCERN_STAT_TREE_SIZE,
+ CONCERN_STAT_BLOB_SIZE,
+ CONCERN_STAT_TAG_SIZE,
+ CONCERN_STAT_COMMIT_LARGEST,
+ CONCERN_STAT_TREE_LARGEST,
+ CONCERN_STAT_BLOB_LARGEST,
+ CONCERN_STAT_TAG_LARGEST,
+ CONCERN_STAT_COMMIT_INFLATED_SIZE,
+ CONCERN_STAT_TREE_INFLATED_SIZE,
+ CONCERN_STAT_BLOB_INFLATED_SIZE,
+ CONCERN_STAT_TAG_INFLATED_SIZE,
+};
+
+struct concern_stat_threshold repo_structure_concern_thresholds[] = {
+ { .name = "commit_count", .threshold = 19 },
+ {.name = "tree_count", .threshold = 21 },
+ { .name = "blob_count", .threshold = 22 },
+ { .name = "tag_count", .threshold = 15 },
+ { .name = "commit_size", .threshold = 28 },
+ { .name = "tree_size", .threshold = 27 },
+ { .name = "blob_size", .threshold = 36 },
+ { .name = "tags_size", .threshold = 18 },
+ { .name = "commit_largest", .threshold = 18 },
+ { .name = "tree_largest", .threshold = 18 },
+ { .name = "blob_largest", .threshold = 26 },
+ { .name = "tag_largest", .threshold = 15 },
+ { .name = "commit_inflated_size", .threshold = 28 },
+ { .name = "tree_inflated_size", .threshold = 32 },
+ { .name = "blob_inflated_size", .threshold = 34 },
+ { .name = "tag_inflated_size", .threshold = 25}
+};
+
+/*
+ * Concern thresholds aren't universal. We try to set reasonable default
+ * values, but projects may want to set these thresholds differently. To
+ * support that, they can set the thresholds for their project in the
+ * project's git config, in the section "repo.structure.concern".
+ */
+static int config_concern_thresholds(const char *prefixed_var,
+ const char *str_val,
+ const struct config_context *ctx,
+ void *cb UNUSED) {
+ const char *config_prefix = "repo.structure.concern.";
+ if (!strncmp(config_prefix, prefixed_var, strlen(config_prefix))) {
+ const char *concern_name = &prefixed_var[strlen(config_prefix)];
+ for (int i = 0; concern_stat_thresholds[i].name != 0; i++) {
+ if (!strcmp(concern_stat_thresholds[i].name, concern_name)) {
+ int value = git_config_int(prefixed_var, str_val, ctx->kvi);
+ if (value < 0)
+ return -1;
+ concern_stat_thresholds[i].threshold = value;
+ return 0;
+ }
+ }
+ warning(_("Invalid concern threshold name: '%s'"), concern_name);
+ return 0;
+ }
+ return 0;
+}
+
+static size_t approx_log2(long value) {
+ int position = 0;
+ if (value <= 0) {
+ return 0;
+ }
+ while (value > 1) {
+ position++;
+ value >>= 1;
+ }
+ return position;
+}
+
+static size_t repo_structure_concern_level(enum concern_stat stat_id,
+ size_t value) {
+ size_t log_value;
+ if (stat_id >= ARRAY_SIZE(concern_stat_thresholds))
+ BUG("unexpected repository stat type");
+ log_value = approx_log2(value);
+ if (log_value > concern_stat_thresholds[stat_id].threshold)
+ return log_value -
+ concern_stat_thresholds[stat_id].threshold;
+ else
+ return 0;
+}
+
/*
* Holds column data that gets stored for each row.
*/
@@ -358,6 +468,7 @@ struct stats_table_entry {
const char *unit;
size_t index;
struct object_id *oid;
+ size_t concern;
};
static void stats_table_vaddf(struct stats_table *table,
@@ -391,6 +502,8 @@ 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->unit) {
int unit_width = utf8_strwidth(entry->unit);
if (unit_width > table->unit_col_width)
@@ -410,13 +523,14 @@ static void stats_table_addf(struct stats_table *table, const char *format, ...)
}
static void stats_table_count_addf(struct stats_table *table, size_t value,
- const char *format, ...)
+ size_t concern, const char *format, ...)
{
struct stats_table_entry *entry;
va_list ap;
CALLOC_ARRAY(entry, 1);
humanise_count(value, &entry->value, &entry->unit);
+ entry->concern = concern;
va_start(ap, format);
stats_table_vaddf(table, entry, format, ap);
@@ -425,13 +539,14 @@ static void stats_table_count_addf(struct stats_table *table, size_t value,
static void stats_table_object_count_addf(struct stats_table *table,
struct object_id *oid, size_t value,
- const char *format, ...)
+ size_t concern, const char *format, ...)
{
struct stats_table_entry *entry;
va_list ap;
CALLOC_ARRAY(entry, 1);
humanise_count(value, &entry->value, &entry->unit);
+ entry->concern = concern;
/*
* A NULL OID should not have a table annotation.
@@ -445,6 +560,7 @@ static void stats_table_object_count_addf(struct stats_table *table,
}
static void stats_table_size_addf(struct stats_table *table, size_t value,
+ size_t concern,
const char *format, ...)
{
struct stats_table_entry *entry;
@@ -452,6 +568,7 @@ static void stats_table_size_addf(struct stats_table *table, size_t value,
CALLOC_ARRAY(entry, 1);
humanise_bytes(value, &entry->value, &entry->unit, HUMANISE_COMPACT);
+ entry->concern = concern;
va_start(ap, format);
stats_table_vaddf(table, entry, format, ap);
@@ -460,6 +577,7 @@ static void stats_table_size_addf(struct stats_table *table, size_t value,
static void stats_table_object_size_addf(struct stats_table *table,
struct object_id *oid, size_t value,
+ size_t concern,
const char *format, ...)
{
struct stats_table_entry *entry;
@@ -467,6 +585,7 @@ static void stats_table_object_size_addf(struct stats_table *table,
CALLOC_ARRAY(entry, 1);
humanise_bytes(value, &entry->value, &entry->unit, HUMANISE_COMPACT);
+ entry->concern = concern;
/*
* A NULL OID should not have a table annotation.
@@ -501,47 +620,92 @@ static void stats_table_setup_structure(struct stats_table *table,
ref_total = get_total_reference_count(refs);
stats_table_addf(table, "* %s", _("References"));
- stats_table_count_addf(table, ref_total, " * %s", _("Count"));
- stats_table_count_addf(table, refs->branches, " * %s", _("Branches"));
- stats_table_count_addf(table, refs->tags, " * %s", _("Tags"));
- stats_table_count_addf(table, refs->remotes, " * %s", _("Remotes"));
- stats_table_count_addf(table, refs->others, " * %s", _("Others"));
+ stats_table_count_addf(table, ref_total, 0,
+ " * %s", _("Count"));
+ stats_table_count_addf(table, refs->branches, 0,
+ " * %s", _("Branches"));
+ stats_table_count_addf(table, refs->tags,
+ repo_structure_concern_level(
+ CONCERN_STAT_TAG_COUNT,
+ refs->tags),
+ " * %s", _("Tags"));
+ stats_table_count_addf(table, refs->remotes, 0,
+ " * %s", _("Remotes"));
+ stats_table_count_addf(table, refs->others, 0,
+ " * %s", _("Others"));
object_count_total = get_total_object_values(&objects->type_counts);
stats_table_addf(table, "");
stats_table_addf(table, "* %s", _("Reachable objects"));
- stats_table_count_addf(table, object_count_total, " * %s", _("Count"));
+ stats_table_count_addf(table, object_count_total, 0,
+ " * %s", _("Count"));
stats_table_count_addf(table, objects->type_counts.commits,
+ repo_structure_concern_level(
+ CONCERN_STAT_COMMIT_COUNT,
+ objects->type_counts.commits),
" * %s", _("Commits"));
stats_table_count_addf(table, objects->type_counts.trees,
+ repo_structure_concern_level(
+ CONCERN_STAT_TREE_COUNT,
+ objects->type_counts.trees),
" * %s", _("Trees"));
stats_table_count_addf(table, objects->type_counts.blobs,
+ repo_structure_concern_level(
+ CONCERN_STAT_BLOB_COUNT,
+ objects->type_counts.blobs),
" * %s", _("Blobs"));
stats_table_count_addf(table, objects->type_counts.tags,
+ repo_structure_concern_level(
+ CONCERN_STAT_TAG_COUNT,
+ objects->type_counts.tags),
" * %s", _("Tags"));
inflated_object_total = get_total_object_values(&objects->inflated_sizes);
- stats_table_size_addf(table, inflated_object_total,
+ stats_table_size_addf(table, inflated_object_total, 0,
" * %s", _("Inflated size"));
stats_table_size_addf(table, objects->inflated_sizes.commits,
+ repo_structure_concern_level(
+ CONCERN_STAT_COMMIT_INFLATED_SIZE,
+ objects->inflated_sizes.commits),
" * %s", _("Commits"));
stats_table_size_addf(table, objects->inflated_sizes.trees,
+ repo_structure_concern_level(
+ CONCERN_STAT_TREE_INFLATED_SIZE,
+ objects->inflated_sizes.trees),
" * %s", _("Trees"));
stats_table_size_addf(table, objects->inflated_sizes.blobs,
+ repo_structure_concern_level(
+ CONCERN_STAT_BLOB_INFLATED_SIZE,
+ objects->inflated_sizes.blobs),
" * %s", _("Blobs"));
stats_table_size_addf(table, objects->inflated_sizes.tags,
+ repo_structure_concern_level(
+ CONCERN_STAT_TAG_INFLATED_SIZE,
+ objects->inflated_sizes.tags),
" * %s", _("Tags"));
disk_object_total = get_total_object_values(&objects->disk_sizes);
- stats_table_size_addf(table, disk_object_total,
+ stats_table_size_addf(table, disk_object_total, 0,
" * %s", _("Disk size"));
stats_table_size_addf(table, objects->disk_sizes.commits,
+ repo_structure_concern_level(
+ CONCERN_STAT_COMMIT_SIZE,
+ objects->disk_sizes.commits),
" * %s", _("Commits"));
stats_table_size_addf(table, objects->disk_sizes.trees,
+ repo_structure_concern_level(
+ CONCERN_STAT_TREE_SIZE,
+ objects->disk_sizes.trees),
" * %s", _("Trees"));
stats_table_size_addf(table, objects->disk_sizes.blobs,
+ repo_structure_concern_level(
+ CONCERN_STAT_BLOB_SIZE,
+ objects->disk_sizes.blobs),
" * %s", _("Blobs"));
stats_table_size_addf(table, objects->disk_sizes.tags,
+ repo_structure_concern_level(
+ CONCERN_STAT_TAG_SIZE,
+ objects->disk_sizes.tags),
" * %s", _("Tags"));
stats_table_addf(table, "");
@@ -550,51 +714,71 @@ static void stats_table_setup_structure(struct stats_table *table,
stats_table_object_size_addf(table,
&objects->largest.commit_size.oid,
objects->largest.commit_size.value,
+ repo_structure_concern_level(
+ CONCERN_STAT_COMMIT_LARGEST,
+ objects->largest.commit_size.value),
" * %s", _("Maximum size"));
stats_table_object_count_addf(table,
&objects->largest.parent_count.oid,
objects->largest.parent_count.value,
+ 0,
" * %s", _("Maximum parents"));
stats_table_addf(table, " * %s", _("Trees"));
stats_table_object_size_addf(table,
&objects->largest.tree_size.oid,
objects->largest.tree_size.value,
+ repo_structure_concern_level(
+ CONCERN_STAT_TREE_LARGEST,
+ objects->largest.tree_size.value),
" * %s", _("Maximum size"));
stats_table_object_count_addf(table,
&objects->largest.tree_entries.oid,
objects->largest.tree_entries.value,
+ 0,
" * %s", _("Maximum entries"));
stats_table_addf(table, " * %s", _("Blobs"));
stats_table_object_size_addf(table,
&objects->largest.blob_size.oid,
objects->largest.blob_size.value,
+ repo_structure_concern_level(
+ CONCERN_STAT_BLOB_LARGEST,
+ objects->largest.blob_size.value),
" * %s", _("Maximum size"));
stats_table_addf(table, " * %s", _("Tags"));
stats_table_object_size_addf(table,
&objects->largest.tag_size.oid,
objects->largest.tag_size.value,
+ repo_structure_concern_level(
+ CONCERN_STAT_TAG_LARGEST,
+ objects->largest.tag_size.value),
" * %s", _("Maximum size"));
}
#define INDEX_WIDTH 4
-static void stats_table_print_structure(const struct stats_table *table)
+static void stats_table_print_structure(const struct stats_table *table,
+ bool show_concern)
{
const char *name_col_title = _("Repository structure");
const char *value_col_title = _("Value");
+ 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);
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;
struct string_list_item *item;
struct strbuf buf = STRBUF_INIT;
+ struct strbuf concern_str = STRBUF_INIT;
if (title_name_width > name_col_width)
name_col_width = title_name_width;
if (title_value_width > value_col_width + unit_col_width + 1)
value_col_width = title_value_width - unit_col_width;
-
+ 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);
@@ -602,6 +786,12 @@ static void stats_table_print_structure(const struct stats_table *table)
strbuf_utf8_align(&buf, ALIGN_LEFT,
value_col_width + unit_col_width + 1, value_col_title);
strbuf_addstr(&buf, " |");
+ if (show_concern) {
+ strbuf_addch(&buf, ' ');
+ strbuf_utf8_align(&buf, ALIGN_LEFT, concern_col_width,
+ concern_col_title);
+ strbuf_addstr(&buf, " |");
+ }
printf("%s\n", buf.buf);
printf("| ");
@@ -610,17 +800,28 @@ static void stats_table_print_structure(const struct stats_table *table)
printf(" | ");
for (int i = 0; i < value_col_width + unit_col_width + 1; i++)
putchar('-');
- printf(" |\n");
+ printf(" |");
+ if (show_concern) {
+ putchar(' ');
+ for (int i = 0; i < concern_col_width; i++) {
+ putchar('-');
+ }
+ printf(" |");
+ }
+ putchar('\n');
for_each_string_list_item(item, &table->rows) {
struct stats_table_entry *entry = item->util;
const char *value = "";
const char *unit = "";
+ strbuf_reset(&concern_str);
if (entry) {
value = entry->value;
if (entry->unit)
unit = entry->unit;
+ if (entry->concern > 0)
+ strbuf_addchars(&concern_str, '*', entry->concern);
}
strbuf_reset(&buf);
@@ -638,6 +839,11 @@ static void stats_table_print_structure(const struct stats_table *table)
strbuf_addch(&buf, ' ');
strbuf_utf8_align(&buf, ALIGN_LEFT, unit_col_width, unit);
strbuf_addstr(&buf, " |");
+ if (show_concern) {
+ strbuf_addch(&buf, ' ');
+ strbuf_utf8_align(&buf, ALIGN_LEFT, concern_col_width, concern_str.buf);
+ strbuf_addstr(&buf, " |");
+ }
printf("%s\n", buf.buf);
}
@@ -681,12 +887,15 @@ static void print_object_data(const char *key, char key_delim,
}
static void structure_keyvalue_print(struct repo_structure *stats,
- char key_delim, char value_delim)
+ char key_delim, char value_delim,
+ bool show_concern)
{
print_keyvalue("references.branches.count", key_delim,
stats->refs.branches, value_delim);
+
print_keyvalue("references.tags.count", key_delim,
stats->refs.tags, value_delim);
+
print_keyvalue("references.remotes.count", key_delim,
stats->refs.remotes, value_delim);
print_keyvalue("references.others.count", key_delim,
@@ -694,39 +903,120 @@ static void structure_keyvalue_print(struct repo_structure *stats,
print_keyvalue("objects.commits.count", key_delim,
stats->objects.type_counts.commits, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.commits.count.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_COMMIT_COUNT,
+ stats->objects.type_counts.commits),
+ value_delim);
print_keyvalue("objects.trees.count", key_delim,
stats->objects.type_counts.trees, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.trees.count.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_TREE_COUNT,
+ stats->objects.type_counts.trees),
+ value_delim);
print_keyvalue("objects.blobs.count", key_delim,
stats->objects.type_counts.blobs, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.blobs.count.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_BLOB_COUNT,
+ stats->objects.type_counts.blobs),
+ value_delim);
print_keyvalue("objects.tags.count", key_delim,
stats->objects.type_counts.tags, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.tags.count.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_TAG_COUNT,
+ stats->objects.type_counts.tags),
+ value_delim);
print_keyvalue("objects.commits.inflated_size", key_delim,
stats->objects.inflated_sizes.commits, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.commits.inflated_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_COMMIT_INFLATED_SIZE,
+ stats->objects.inflated_sizes.commits),
+ value_delim);
print_keyvalue("objects.trees.inflated_size", key_delim,
stats->objects.inflated_sizes.trees, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.trees.inflated_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_TREE_INFLATED_SIZE,
+ stats->objects.inflated_sizes.trees),
+ value_delim);
+
print_keyvalue("objects.blobs.inflated_size", key_delim,
stats->objects.inflated_sizes.blobs, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.blobs.inflated_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_BLOB_INFLATED_SIZE,
+ stats->objects.inflated_sizes.blobs),
+ value_delim);
print_keyvalue("objects.tags.inflated_size", key_delim,
stats->objects.inflated_sizes.tags, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.tags.inflated_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_TAG_INFLATED_SIZE,
+ stats->objects.inflated_sizes.tags),
+ value_delim);
print_keyvalue("objects.commits.disk_size", key_delim,
stats->objects.disk_sizes.commits, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.commits.disk_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_COMMIT_SIZE,
+ stats->objects.disk_sizes.commits),
+ value_delim);
print_keyvalue("objects.trees.disk_size", key_delim,
stats->objects.disk_sizes.trees, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.trees.disk_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_TREE_SIZE,
+ stats->objects.disk_sizes.trees),
+ value_delim);
print_keyvalue("objects.blobs.disk_size", key_delim,
stats->objects.disk_sizes.blobs, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.blobs.blob_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_BLOB_SIZE,
+ stats->objects.disk_sizes.blobs),
+ 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),
+ value_delim);
print_object_data("objects.commits.max_size", key_delim,
&stats->objects.largest.commit_size, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.commits.max_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_COMMIT_LARGEST,
+ stats->objects.largest.commit_size.value),
+ value_delim);
print_object_data("objects.trees.max_size", key_delim,
&stats->objects.largest.tree_size, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.trees.max_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_TREE_LARGEST,
+ stats->objects.largest.tree_size.value),
+ value_delim);
print_object_data("objects.blobs.max_size", key_delim,
&stats->objects.largest.blob_size, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.blobs.max_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_BLOB_LARGEST,
+ stats->objects.largest.blob_size.value),
+ value_delim);
print_object_data("objects.tags.max_size", key_delim,
&stats->objects.largest.tag_size, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.tags.max_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_TAG_LARGEST,
+ stats->objects.largest.tag_size.value),
+ value_delim);
print_object_data("objects.commits.max_parents", key_delim,
&stats->objects.largest.parent_count, value_delim);
@@ -935,6 +1225,8 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
struct repo_structure stats = { 0 };
struct rev_info revs;
int show_progress = -1;
+ int show_concern = 0;
+
struct option options[] = {
OPT_CALLBACK_F(0, "format", &format, N_("format"),
N_("output format"),
@@ -944,6 +1236,7 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
PARSE_OPT_NONEG | PARSE_OPT_NOARG,
parse_format_cb),
OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
+ OPT_BOOL(0, "concern", &show_concern, N_("show level of concern")),
OPT_END()
};
@@ -962,13 +1255,13 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
switch (format) {
case FORMAT_TABLE:
stats_table_setup_structure(&table, &stats);
- stats_table_print_structure(&table);
+ stats_table_print_structure(&table, show_concern);
break;
case FORMAT_NEWLINE_TERMINATED:
- structure_keyvalue_print(&stats, '=', '\n');
+ structure_keyvalue_print(&stats, '=', '\n', show_concern);
break;
case FORMAT_NUL_TERMINATED:
- structure_keyvalue_print(&stats, '\n', '\0');
+ structure_keyvalue_print(&stats, '\n', '\0', show_concern);
break;
default:
BUG("invalid output format");
diff --git a/sideband.c b/sideband.c
index 374187402e..0e75ab2317 100644
--- a/sideband.c
+++ b/sideband.c
@@ -1,4 +1,4 @@
-#define USE_THE_REPOSITORY_VARIABLE
+define USE_THE_REPOSITORY_VARIABLE
#define DISABLE_SIGN_COMPARE_WARNINGS
#include "git-compat-util.h"
diff --git a/t/t1901-repo-structure.sh b/t/t1901-repo-structure.sh
index 02cc2b594a..f1e3fd2a75 100755
--- a/t/t1901-repo-structure.sh
+++ b/t/t1901-repo-structure.sh
@@ -67,77 +67,7 @@ test_expect_success 'empty repository' '
EOF
git repo structure >out 2>err &&
-
- test_cmp expect out &&
- test_line_count = 0 err
- )
-'
-
-test_expect_success SHA1 'repository with references and objects' '
- test_when_finished "rm -rf repo" &&
- git init repo &&
- (
- cd repo &&
- test_commit_bulk 1005 &&
- git tag -a foo -m bar &&
-
- oid="$(git rev-parse HEAD)" &&
- git update-ref refs/remotes/origin/foo "$oid" &&
-
- # Also creates a commit, tree, and blob.
- git notes add -m foo &&
-
- # The tags disk size is handled specially due to the
- # git-rev-list(1) --disk-usage=human option printing the full
- # "byte/bytes" unit string instead of just "B".
- cat >expect <<-EOF &&
- | Repository structure | Value |
- | ------------------------- | ---------- |
- | * References | |
- | * Count | 4 |
- | * Branches | 1 |
- | * Tags | 1 |
- | * Remotes | 1 |
- | * Others | 1 |
- | | |
- | * Reachable objects | |
- | * Count | 3.02 k |
- | * Commits | 1.01 k |
- | * Trees | 1.01 k |
- | * Blobs | 1.01 k |
- | * Tags | 1 |
- | * Inflated size | 16.03 MiB |
- | * Commits | 217.92 KiB |
- | * Trees | 15.81 MiB |
- | * Blobs | 11.68 KiB |
- | * Tags | 132 B |
- | * Disk size | $(object_type_disk_usage all true) |
- | * Commits | $(object_type_disk_usage commit true) |
- | * Trees | $(object_type_disk_usage tree true) |
- | * Blobs | $(object_type_disk_usage blob true) |
- | * Tags | $(object_type_disk_usage tag) B |
- | | |
- | * Largest objects | |
- | * Commits | |
- | * Maximum size [1] | 223 B |
- | * Maximum parents [2] | 1 |
- | * Trees | |
- | * Maximum size [3] | 32.29 KiB |
- | * Maximum entries [4] | 1.01 k |
- | * Blobs | |
- | * Maximum size [5] | 13 B |
- | * Tags | |
- | * Maximum size [6] | 132 B |
-
- [1] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a
- [2] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a
- [3] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c
- [4] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c
- [5] 97d808e45116bf02103490294d3d46dad7a2ac62
- [6] 4dae4f5954f5e6feb3577cfb1b181daa3fd3afd2
- EOF
-
- git repo structure >out 2>err &&
+ cp out /tmp/test1-out &&
test_cmp expect out &&
test_line_count = 0 err
@@ -204,6 +134,59 @@ test_expect_success SHA1 'lines and nul format' '
)
'
+test_expect_success SHA1 'lines format with concerns' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ # configure thresholds to get concern indications
+ git config repo.structure.concern.commit-count 7 &&
+ git config repo.structure.concern.commit-size 14 &&
+ git config repo.structure.concern.tree-size 10 &&
+ git config repo.structure.concern.blob-size 8 &&
+ git config repo.structure.concern.blob-inflated-size 10 &&
+ git config repo.structure.concern.tag-size 2 &&
+ git config repo.structure.concern.commit-largest 4 &&
+ git config repo.structure.concern.blob-largest 2 &&
+ touch configured &&
+ test_commit_bulk 1005 &&
+ git tag -a foo -m bar &&
+ oid="$(git rev-parse HEAD)" &&
+ git update-ref refs/remotes/origin/foo "$oid" &&
+
+ # Also creates a commit, tree, and blob.
+ git notes add -m foo &&
+
+
+ # The tags disk size is handled specially due to the
+ # git-rev-list(1) --disk-usage=human option printing the full
+ # "byte/bytes" unit string instead of just "B".
+ cat >expect <<-EOF &&
+ objects.commits.count.concern_level=2
+ objects.trees.count.concern_level=0
+ objects.blobs.count.concern_level=0
+ objects.tags.count.concern_level=0
+ objects.commits.inflated_size.concern_level=0
+ objects.trees.inflated_size.concern_level=0
+ objects.blobs.inflated_size.concern_level=3
+ objects.tags.inflated_size.concern_level=0
+ objects.commits.disk_size.concern_level=3
+ objects.trees.disk_size.concern_level=8
+ objects.blobs.blob_size.concern_level=6
+ objects.tags.disk_size.concern_level=4
+ objects.commits.max_size.concern_level=3
+ objects.trees.max_size.concern_level=0
+ objects.blobs.max_size.concern_level=1
+ objects.tags.max_size.concern_level=0
+ EOF
+
+ git repo structure --concern --format=lines >actual 2>err &&
+ test_grep "concern_level" actual >actual-concerns &&
+ test_cmp expect actual-concerns &&
+ test_line_count = 0 err
+ )
+'
+
test_expect_success 'progress meter option' '
test_when_finished "rm -rf repo" &&
git init repo &&
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v2 2/3] repo: make concern thresholds configurable
2026-10-26 17:43 ` [PATCH v2 0/3] repo: add degree-of-concern reporting to structure Mark C. Chu-Carroll
2026-10-26 17:43 ` [PATCH v2 1/3] repo: add level of concern reporting Mark C. Chu-Carroll
@ 2026-10-26 17:43 ` Mark C. Chu-Carroll
2026-10-27 14:55 ` Patrick Steinhardt
2026-10-26 17:43 ` [PATCH v2 3/3] repo: add documentation for level-of-concern Mark C. Chu-Carroll
2 siblings, 1 reply; 15+ messages in thread
From: Mark C. Chu-Carroll @ 2026-10-26 17:43 UTC (permalink / raw)
To: git; +Cc: Mark C. Chu-Carroll
The concern reporting mechanism so far provides a
measure of concern based on heuristic values estimated
by analyzing a collection of (hopefully) representative
repositories. But for different projects/usecases, the
thresholds at which a value becomes concerning may vary.
Provide a mechanism using git config to allow users to
configure per-repo concern levels for "git repo structure"
concern reporting.
---
builtin/repo.c | 62 +++++++++-------
sideband.c | 2 +-
t/t1901-repo-structure.sh | 180 ++++++++++++++++++++++++++++++++++++++++++----
3 files changed, 203 insertions(+), 41 deletions(-)
diff --git a/builtin/repo.c b/builtin/repo.c
index a450497d4e..ed579e6685 100644
--- a/builtin/repo.c
+++ b/builtin/repo.c
@@ -2,6 +2,7 @@
#include "builtin.h"
#include "commit.h"
+#include "config.h"
#include "environment.h"
#include "hash.h"
#include "hex.h"
@@ -29,6 +30,8 @@
#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,
@@ -386,40 +389,43 @@ enum concern_stat {
CONCERN_STAT_TREE_INFLATED_SIZE,
CONCERN_STAT_BLOB_INFLATED_SIZE,
CONCERN_STAT_TAG_INFLATED_SIZE,
+ CONCERN_STAT_MAXIMUM
};
-struct concern_stat_threshold repo_structure_concern_thresholds[] = {
- { .name = "commit_count", .threshold = 19 },
- {.name = "tree_count", .threshold = 21 },
- { .name = "blob_count", .threshold = 22 },
- { .name = "tag_count", .threshold = 15 },
- { .name = "commit_size", .threshold = 28 },
- { .name = "tree_size", .threshold = 27 },
- { .name = "blob_size", .threshold = 36 },
- { .name = "tags_size", .threshold = 18 },
- { .name = "commit_largest", .threshold = 18 },
- { .name = "tree_largest", .threshold = 18 },
- { .name = "blob_largest", .threshold = 26 },
- { .name = "tag_largest", .threshold = 15 },
- { .name = "commit_inflated_size", .threshold = 28 },
- { .name = "tree_inflated_size", .threshold = 32 },
- { .name = "blob_inflated_size", .threshold = 34 },
- { .name = "tag_inflated_size", .threshold = 25}
+struct concern_stat_threshold concern_stat_thresholds[] = {
+ { .name = "commit-count", .threshold = 19 },
+ { .name = "tree-count", .threshold = 21 },
+ { .name = "blob-count", .threshold = 22 },
+ { .name = "tag-count", .threshold = 15 },
+ { .name = "commit-size", .threshold = 28 },
+ { .name = "tree-size", .threshold = 27 },
+ { .name = "blob-size", .threshold = 36 },
+ { .name = "tag-size", .threshold = 18 },
+ { .name = "commit-largest", .threshold = 18 },
+ { .name = "tree-largest", .threshold = 18 },
+ { .name = "blob-largest", .threshold = 26 },
+ { .name = "tag-largest", .threshold = 15 },
+ { .name = "commit-inflated-size", .threshold = 28 },
+ { .name = "tree-inflated-size", .threshold = 32 },
+ { .name = "blob-inflated-size", .threshold = 34 },
+ { .name = "tag-inflated-size", .threshold = 25},
+ { 0 }
};
/*
* Concern thresholds aren't universal. We try to set reasonable default
* values, but projects may want to set these thresholds differently. To
* support that, they can set the thresholds for their project in the
- * project's git config, in the section "repo.structure.concern".
+ * project's git config, in the section "repo.concern".
*/
static int config_concern_thresholds(const char *prefixed_var,
const char *str_val,
const struct config_context *ctx,
void *cb UNUSED) {
- const char *config_prefix = "repo.structure.concern.";
- if (!strncmp(config_prefix, prefixed_var, strlen(config_prefix))) {
- const char *concern_name = &prefixed_var[strlen(config_prefix)];
+ if (!strncmp(REPO_STRUCTURE_CONFIG_PREFIX, prefixed_var,
+ strlen(REPO_STRUCTURE_CONFIG_PREFIX))) {
+ const char *concern_name =
+ &prefixed_var[strlen(REPO_STRUCTURE_CONFIG_PREFIX)];
for (int i = 0; concern_stat_thresholds[i].name != 0; i++) {
if (!strcmp(concern_stat_thresholds[i].name, concern_name)) {
int value = git_config_int(prefixed_var, str_val, ctx->kvi);
@@ -435,7 +441,7 @@ static int config_concern_thresholds(const char *prefixed_var,
return 0;
}
-static size_t approx_log2(long value) {
+static size_t approx_log2(size_t value) {
int position = 0;
if (value <= 0) {
return 0;
@@ -450,7 +456,7 @@ static size_t approx_log2(long value) {
static size_t repo_structure_concern_level(enum concern_stat stat_id,
size_t value) {
size_t log_value;
- if (stat_id >= ARRAY_SIZE(concern_stat_thresholds))
+ if (stat_id >= CONCERN_STAT_MAXIMUM)
BUG("unexpected repository stat type");
log_value = approx_log2(value);
if (log_value > concern_stat_thresholds[stat_id].threshold)
@@ -844,6 +850,7 @@ 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);
}
@@ -977,7 +984,7 @@ static void structure_keyvalue_print(struct repo_structure *stats,
print_keyvalue("objects.blobs.disk_size", key_delim,
stats->objects.disk_sizes.blobs, value_delim);
if (show_concern)
- print_keyvalue("objects.blobs.blob_size.concern_level", key_delim,
+ print_keyvalue("objects.blobs.disk_size.concern_level", key_delim,
repo_structure_concern_level(CONCERN_STAT_BLOB_SIZE,
stats->objects.disk_sizes.blobs),
value_delim);
@@ -997,7 +1004,7 @@ static void structure_keyvalue_print(struct repo_structure *stats,
stats->objects.largest.commit_size.value),
value_delim);
print_object_data("objects.trees.max_size", key_delim,
- &stats->objects.largest.tree_size, value_delim);
+ &stats->objects.largest.tree_size, value_delim);
if (show_concern)
print_keyvalue("objects.trees.max_size.concern_level", key_delim,
repo_structure_concern_level(CONCERN_STAT_TREE_LARGEST,
@@ -1011,7 +1018,7 @@ static void structure_keyvalue_print(struct repo_structure *stats,
stats->objects.largest.blob_size.value),
value_delim);
print_object_data("objects.tags.max_size", key_delim,
- &stats->objects.largest.tag_size, value_delim);
+ &stats->objects.largest.tag_size, value_delim);
if (show_concern)
print_keyvalue("objects.tags.max_size.concern_level", key_delim,
repo_structure_concern_level(CONCERN_STAT_TAG_LARGEST,
@@ -1243,7 +1250,8 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
argc = parse_options(argc, argv, prefix, options, repo_structure_usage, 0);
if (argc)
usage(_("too many arguments"));
-
+ if (show_concern)
+ repo_config(repo, &config_concern_thresholds, NULL);
repo_init_revisions(repo, &revs, prefix);
if (show_progress < 0)
diff --git a/sideband.c b/sideband.c
index 0e75ab2317..374187402e 100644
--- a/sideband.c
+++ b/sideband.c
@@ -1,4 +1,4 @@
-define USE_THE_REPOSITORY_VARIABLE
+#define USE_THE_REPOSITORY_VARIABLE
#define DISABLE_SIGN_COMPARE_WARNINGS
#include "git-compat-util.h"
diff --git a/t/t1901-repo-structure.sh b/t/t1901-repo-structure.sh
index f1e3fd2a75..e8f1269ad4 100755
--- a/t/t1901-repo-structure.sh
+++ b/t/t1901-repo-structure.sh
@@ -67,13 +67,166 @@ 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
)
'
+test_expect_success SHA1 'repository with references and objects' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit_bulk 1005 &&
+ git tag -a foo -m bar &&
+
+ oid="$(git rev-parse HEAD)" &&
+ git update-ref refs/remotes/origin/foo "$oid" &&
+
+ # Also creates a commit, tree, and blob.
+ git notes add -m foo &&
+
+ # The tags disk size is handled specially due to the
+ # git-rev-list(1) --disk-usage=human option printing the full
+ # "byte/bytes" unit string instead of just "B".
+ cat >expect <<-EOF &&
+ | Repository structure | Value |
+ | ------------------------- | ---------- |
+ | * References | |
+ | * Count | 4 |
+ | * Branches | 1 |
+ | * Tags | 1 |
+ | * Remotes | 1 |
+ | * Others | 1 |
+ | | |
+ | * Reachable objects | |
+ | * Count | 3.02 k |
+ | * Commits | 1.01 k |
+ | * Trees | 1.01 k |
+ | * Blobs | 1.01 k |
+ | * Tags | 1 |
+ | * Inflated size | 16.03 MiB |
+ | * Commits | 217.92 KiB |
+ | * Trees | 15.81 MiB |
+ | * Blobs | 11.68 KiB |
+ | * Tags | 132 B |
+ | * Disk size | $(object_type_disk_usage all true) |
+ | * Commits | $(object_type_disk_usage commit true) |
+ | * Trees | $(object_type_disk_usage tree true) |
+ | * Blobs | $(object_type_disk_usage blob true) |
+ | * Tags | $(object_type_disk_usage tag) B |
+ | | |
+ | * Largest objects | |
+ | * Commits | |
+ | * Maximum size [1] | 223 B |
+ | * Maximum parents [2] | 1 |
+ | * Trees | |
+ | * Maximum size [3] | 32.29 KiB |
+ | * Maximum entries [4] | 1.01 k |
+ | * Blobs | |
+ | * Maximum size [5] | 13 B |
+ | * Tags | |
+ | * Maximum size [6] | 132 B |
+
+ [1] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a
+ [2] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a
+ [3] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c
+ [4] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c
+ [5] 97d808e45116bf02103490294d3d46dad7a2ac62
+ [6] 4dae4f5954f5e6feb3577cfb1b181daa3fd3afd2
+ EOF
+
+ git repo structure >out 2>err &&
+
+ test_cmp expect out &&
+ test_line_count = 0 err
+ )
+'
+
+test_expect_success SHA1 'concern flagging' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ # differences between zlib and zlib-ng can cause this
+ # 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 &&
+ test_commit_bulk 1005 &&
+ git tag -a foo -m bar &&
+ oid="$(git rev-parse HEAD)" &&
+ git update-ref refs/remotes/origin/foo "$oid" &&
+
+ # Also creates a commit, tree, and blob.
+ git notes add -m foo &&
+
+
+ # The tags disk size is handled specially due to the
+ # git-rev-list(1) --disk-usage=human option printing the full
+ # "byte/bytes" unit string instead of just "B".
+ cat >expect <<-EOF &&
+ | Repository structure | Value | Concern |
+ | ------------------------- | ---------- | -------- |
+ | * References | | |
+ | * Count | 4 | |
+ | * Branches | 1 | |
+ | * Tags | 1 | |
+ | * Remotes | 1 | |
+ | * Others | 1 | |
+ | | | |
+ | * Reachable objects | | |
+ | * Count | 3.02 k | |
+ | * Commits | 1.01 k | ** |
+ | * Trees | 1.01 k | |
+ | * Blobs | 1.01 k | |
+ | * Tags | 1 | |
+ | * Inflated size | 16.03 MiB | |
+ | * Commits | 217.92 KiB | |
+ | * Trees | 15.81 MiB | |
+ | * Blobs | 11.68 KiB | *** |
+ | * Tags | 132 B | |
+ | * Disk size | $(object_type_disk_usage all true) | |
+ | * Commits | $(object_type_disk_usage commit true) | *** |
+ | * Trees | $(object_type_disk_usage tree true) | ******** |
+ | * Blobs | $(object_type_disk_usage blob true) | ****** |
+ | * Tags | $(object_type_disk_usage tag) B | ***** |
+ | | | |
+ | * Largest objects | | |
+ | * Commits | | |
+ | * Maximum size [1] | 223 B | *** |
+ | * Maximum parents [2] | 1 | |
+ | * Trees | | |
+ | * Maximum size [3] | 32.29 KiB | |
+ | * Maximum entries [4] | 1.01 k | |
+ | * Blobs | | |
+ | * Maximum size [5] | 13 B | * |
+ | * Tags | | |
+ | * Maximum size [6] | 132 B | |
+
+ [1] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a
+ [2] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a
+ [3] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c
+ [4] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c
+ [5] 97d808e45116bf02103490294d3d46dad7a2ac62
+ [6] 4dae4f5954f5e6feb3577cfb1b181daa3fd3afd2
+ EOF
+
+ git repo structure --concern >actual 2>err &&
+
+ test_cmp expect actual &&
+ test_line_count = 0 err
+ )
+'
+
test_expect_success SHA1 'lines and nul format' '
test_when_finished "rm -rf repo" &&
git init repo &&
@@ -139,16 +292,18 @@ test_expect_success SHA1 'lines format with concerns' '
git init repo &&
(
cd repo &&
+ # differences between zlib and zlib-ng can cause this
+ # test to fail; so we just turn it off.
+ git config core.compression 0 &&
# configure thresholds to get concern indications
- git config repo.structure.concern.commit-count 7 &&
- git config repo.structure.concern.commit-size 14 &&
- git config repo.structure.concern.tree-size 10 &&
- git config repo.structure.concern.blob-size 8 &&
- git config repo.structure.concern.blob-inflated-size 10 &&
- git config repo.structure.concern.tag-size 2 &&
- git config repo.structure.concern.commit-largest 4 &&
- git config repo.structure.concern.blob-largest 2 &&
- touch configured &&
+ git config repo.concern.commit-count 7 &&
+ git config repo.concern.commit-size 14 &&
+ git config repo.concern.tree-size 10 &&
+ 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 &&
test_commit_bulk 1005 &&
git tag -a foo -m bar &&
oid="$(git rev-parse HEAD)" &&
@@ -157,7 +312,6 @@ test_expect_success SHA1 'lines format with concerns' '
# Also creates a commit, tree, and blob.
git notes add -m foo &&
-
# The tags disk size is handled specially due to the
# git-rev-list(1) --disk-usage=human option printing the full
# "byte/bytes" unit string instead of just "B".
@@ -172,8 +326,8 @@ test_expect_success SHA1 'lines format with concerns' '
objects.tags.inflated_size.concern_level=0
objects.commits.disk_size.concern_level=3
objects.trees.disk_size.concern_level=8
- objects.blobs.blob_size.concern_level=6
- objects.tags.disk_size.concern_level=4
+ objects.blobs.disk_size.concern_level=6
+ objects.tags.disk_size.concern_level=5
objects.commits.max_size.concern_level=3
objects.trees.max_size.concern_level=0
objects.blobs.max_size.concern_level=1
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v2 3/3] repo: add documentation for level-of-concern.
2026-10-26 17:43 ` [PATCH v2 0/3] repo: add degree-of-concern reporting to structure Mark C. Chu-Carroll
2026-10-26 17:43 ` [PATCH v2 1/3] repo: add level of concern reporting Mark C. Chu-Carroll
2026-10-26 17:43 ` [PATCH v2 2/3] repo: make concern thresholds configurable Mark C. Chu-Carroll
@ 2026-10-26 17:43 ` Mark C. Chu-Carroll
2026-10-27 14:55 ` Patrick Steinhardt
2 siblings, 1 reply; 15+ messages in thread
From: Mark C. Chu-Carroll @ 2026-10-26 17:43 UTC (permalink / raw)
To: git; +Cc: Mark C. Chu-Carroll
---
Documentation/config/repo.adoc | 59 ++++++++++++++++++++++++++++++++++++++++++
Documentation/git-repo.adoc | 27 +++++++++++++++++--
2 files changed, 84 insertions(+), 2 deletions(-)
diff --git a/Documentation/config/repo.adoc b/Documentation/config/repo.adoc
new file mode 100644
index 0000000000..811b099d72
--- /dev/null
+++ b/Documentation/config/repo.adoc
@@ -0,0 +1,59 @@
+`repo.concern.commit-count`::
+ Sets the value of the concern threshold for commit count.
+
+`repo.concern.tree-count`::
+ Sets the value of the concern threshold for tree count.
+
+`repo.concern.blob-count`::
+ Sets the value of the concern threshold for blob count.
+
+`repo.concern.tag-count`::
+ Sets the value of the concern threshold for tag count.
+
+`repo.concern.commit-size`::
+ Sets the value of the concern threshold for total commit size.
+
+`repo.concern.tree-size`::
+ Sets the value of the concern threshold for total tree size.
+
+`repo.concern.blob-size`::
+ Sets the value of the concern threshold for total blob size.
+
+`repo.concern.tag-size`::
+ Sets the value of the concern threshold for total tag size.
+
+`repo.concern.commit-inflated-size`::
+ Sets the value of the concern threshold for total size
+ of all inflated commits.
+
+`repo.concern.tree-inflated-size`::
+ Sets the value of the concern threshold for total size of
+ all inflated trees.
+
+`repo.concern.blob-inflated-size`::
+ Sets the value of the concern threshold for total size of
+ all inflated blobs.
+
+`repo.concern.tag-inflated-size`::
+ Sets the value of the concern threshold for the total size
+ of all inflated tags.
+
+`repo.concern.commit-largest`::
+ Sets the value of the concern threshold the size of the largest
+ commit.
+
+`repo.concern.tree-largest`::
+ Sets the value of the concern threshold for the size of the
+ largest tree.
+
+`repo.concern.blob-largest`::
+ Sets the value of the concern threshold for the size of the
+ largest blob.
+
+`repo.concern.tag-largest`::
+ 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.
diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc
index ed7d80c690..bc866c88a8 100644
--- a/Documentation/git-repo.adoc
+++ b/Documentation/git-repo.adoc
@@ -10,7 +10,7 @@ SYNOPSIS
[synopsis]
git repo info [--format=(lines|nul) | -z] [--all | <key>...]
git repo info --keys [--format=(lines|nul) | -z]
-git repo structure [--format=(table|lines|nul) | -z]
+git repo structure [--format=(table|lines|nul) | -z] [--[no-]concern]
DESCRIPTION
-----------
@@ -56,7 +56,7 @@ supported:
`nul`:::
Similar to `lines`, but using a _NUL_ character after each value.
-`structure [--format=(table|lines|nul) | -z]`::
+`structure [--format=(table|lines|nul) | -z] [--[no-]concern]`::
Retrieve statistics about the current repository structure. The
following kinds of information are reported:
+
@@ -88,6 +88,20 @@ supported:
quoted.
+
`-z` is an alias for `--format=nul`.
++
+To make it easier to know when one of the values is something that
+users should be concerned about, adding '--concern' will provide a
+level of concern to be added to the output. In 'table' mode, this will
+be a table column indicating the level of concern as a number of stars;
+in `lines` or `nul` mode, a `concern_level` line will be added for each
+statistic with a numeric value.
++
+The level of concern for each statistic is computed using the log_2 of the
+statistic value. The value is considered concerning if it exceeds a heuristically
+chosen threshold value; the degree of concern is the difference between the
+log_2 of the value, and the log_2 of the corresponding threshold.
++
+The threshold values are configurable; see the configuration section below.
INFO KEYS
---------
@@ -141,6 +155,15 @@ using the `nul` format:
git repo info --format=nul layout.bare layout.shallow
------------
+CONFIGURATION
+-------------
+
+include::includes/cmd-config-section-all.adoc[]
+
+:git-repo: 1
+include::config/repo.adoc[]
+
+
SEE ALSO
--------
linkgit:git-rev-parse[1]
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [PATCH v2 1/3] repo: add level of concern reporting
2026-10-26 17:43 ` [PATCH v2 1/3] repo: add level of concern reporting Mark C. Chu-Carroll
@ 2026-10-27 14:55 ` Patrick Steinhardt
0 siblings, 0 replies; 15+ messages in thread
From: Patrick Steinhardt @ 2026-10-27 14:55 UTC (permalink / raw)
To: Mark C. Chu-Carroll; +Cc: git
On Mon, Oct 26, 2026 at 01:43:54PM -0400, Mark C. Chu-Carroll wrote:
> "git repo structure" computes a set of statistics
> that are useful for diagnosing performance issues in
> a git repository. But for a typical user who hasn't
> encountered any issues yet, it can be difficult to
> interpret the data - when should a value be considered
> concerning?
>
> Add an option to "git repo structure" that adds a "degree
> of concern" column to the structure report, which indicates
> when a stat values has reached a point where it might be
> concerning, and indicate how severe the concern should be.
Missing your Signed-off-by.
I think the commit message should also mention git-sizer(1) here, which
is the direct inspiration that we have for this additional data. To the
best of my knowledge, the level of concern is essentially the last item
that is missing for feature compatibility between these two tools.
Another interesting thing to document here is how you picked the level
of concern in the first place.
> diff --git a/builtin/repo.c b/builtin/repo.c
> index 84e012f83f..a450497d4e 100644
> --- a/builtin/repo.c
> +++ b/builtin/repo.c
> @@ -348,8 +348,118 @@ struct stats_table {
> int name_col_width;
> int value_col_width;
> int unit_col_width;
> + int concern_col_width;
> };
>
> +/*
> + * For computing level of concern about a stat, we need to have some
> + * baseline values. For each of these, we'define a minimum level
> + * of concern, which will appear as 1 star. Then we scale exponentially:
> + * each time that value doubles, we add another star.
> + *
> + * The threshold values for concern are expressed as log_2 values of the size
> + * at which the statistic becomes concerning. The default values for the
> + * threshold were selected based on observed values from a collection of
> + * representative-ish repositories of varying size, by finding the mean log_2
> + * value and standard deviation (sigma) for each statistic over the collection.
> + * The threshold for each metric is then set at mean(log_2) + sigma.
> + */
> +struct concern_stat_threshold {
> + const char *name;
> + size_t threshold;
> +};
> +
> +enum concern_stat {
> + CONCERN_STAT_COMMIT_COUNT,
> + CONCERN_STAT_TREE_COUNT,
> + CONCERN_STAT_BLOB_COUNT,
> + CONCERN_STAT_TAG_COUNT,
> + CONCERN_STAT_COMMIT_SIZE,
> + CONCERN_STAT_TREE_SIZE,
> + CONCERN_STAT_BLOB_SIZE,
> + CONCERN_STAT_TAG_SIZE,
> + CONCERN_STAT_COMMIT_LARGEST,
> + CONCERN_STAT_TREE_LARGEST,
> + CONCERN_STAT_BLOB_LARGEST,
> + CONCERN_STAT_TAG_LARGEST,
> + CONCERN_STAT_COMMIT_INFLATED_SIZE,
> + CONCERN_STAT_TREE_INFLATED_SIZE,
> + CONCERN_STAT_BLOB_INFLATED_SIZE,
> + CONCERN_STAT_TAG_INFLATED_SIZE,
> +};
> +
> +struct concern_stat_threshold repo_structure_concern_thresholds[] = {
> + { .name = "commit_count", .threshold = 19 },
> + {.name = "tree_count", .threshold = 21 },
Nit: missing space.
> + { .name = "blob_count", .threshold = 22 },
> + { .name = "tag_count", .threshold = 15 },
> + { .name = "commit_size", .threshold = 28 },
> + { .name = "tree_size", .threshold = 27 },
> + { .name = "blob_size", .threshold = 36 },
> + { .name = "tags_size", .threshold = 18 },
> + { .name = "commit_largest", .threshold = 18 },
> + { .name = "tree_largest", .threshold = 18 },
> + { .name = "blob_largest", .threshold = 26 },
> + { .name = "tag_largest", .threshold = 15 },
> + { .name = "commit_inflated_size", .threshold = 28 },
> + { .name = "tree_inflated_size", .threshold = 32 },
> + { .name = "blob_inflated_size", .threshold = 34 },
> + { .name = "tag_inflated_size", .threshold = 25}
> +};
I think this is a tiny bit unwieldy, as you now have to remember to
update multiple sites whenever you add another metric to this command.
Ideally, we'd have a single structure that knows to track all
information that we're about to print so that we can define this in a
single, central location.
For example, we could update `struct object_data` to have a configurable
threshold, and then do the same for other such structures. But that
might also get unwieldy by itself.
I think what bugs me a bit is that we have not only two, but three
locations because we have the enum, the thresholds and then also the
data structures themselves.
I'm not quite sure how to fix that myself though.
> +/*
> + * Concern thresholds aren't universal. We try to set reasonable default
> + * values, but projects may want to set these thresholds differently. To
> + * support that, they can set the thresholds for their project in the
> + * project's git config, in the section "repo.structure.concern".
> + */
> +static int config_concern_thresholds(const char *prefixed_var,
> + const char *str_val,
> + const struct config_context *ctx,
> + void *cb UNUSED) {
We should probably use `cb` to pass around the structure that you
populate so that we can avoid global variables.
> + const char *config_prefix = "repo.structure.concern.";
> + if (!strncmp(config_prefix, prefixed_var, strlen(config_prefix))) {
We have `starts_with()`. But also note that if this is a config key then
it should match case-insensitively, so it should rather be
`istarts_with()`.
The config keys are also a bit off as our format only has nested keys up
to three levels deep, not four.
> + const char *concern_name = &prefixed_var[strlen(config_prefix)];
> + for (int i = 0; concern_stat_thresholds[i].name != 0; i++) {
We typically don't compare with a zero value directly, and indices
should be of type `size_t` when iterating, so this should rather be:
for (size_t i = 0; concern_stat_thresholds[i].name; i++)
But the loop condition is a bit weird, because you don't have a final
entry there that has a guaranteed NULL name. So shouldn't we rather
iterate through `ARRAY_SIZE()`?
Also, I may be blind, but where is that struct getting defined? I can
only see `repo_structure_concern_thresholds`.
> + if (!strcmp(concern_stat_thresholds[i].name, concern_name)) {
> + int value = git_config_int(prefixed_var, str_val, ctx->kvi);
> + if (value < 0)
> + return -1;
> + concern_stat_thresholds[i].threshold = value;
> + return 0;
> + }
> + }
> + warning(_("Invalid concern threshold name: '%s'"), concern_name);
> + return 0;
> + }
> + return 0;
> +}
> +
> +static size_t approx_log2(long value) {
We have `log2u()` in "wrapper.h". Can we use that instead?
> @@ -681,12 +887,15 @@ static void print_object_data(const char *key, char key_delim,
> }
>
> static void structure_keyvalue_print(struct repo_structure *stats,
> - char key_delim, char value_delim)
> + char key_delim, char value_delim,
> + bool show_concern)
> {
> print_keyvalue("references.branches.count", key_delim,
> stats->refs.branches, value_delim);
> +
> print_keyvalue("references.tags.count", key_delim,
> stats->refs.tags, value_delim);
> +
Nit: stray newlines.
> diff --git a/sideband.c b/sideband.c
> index 374187402e..0e75ab2317 100644
> --- a/sideband.c
> +++ b/sideband.c
> @@ -1,4 +1,4 @@
> -#define USE_THE_REPOSITORY_VARIABLE
> +define USE_THE_REPOSITORY_VARIABLE
> #define DISABLE_SIGN_COMPARE_WARNINGS
>
> #include "git-compat-util.h"
This change probably shouldn't be here.
Patrick
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2 2/3] repo: make concern thresholds configurable
2026-10-26 17:43 ` [PATCH v2 2/3] repo: make concern thresholds configurable Mark C. Chu-Carroll
@ 2026-10-27 14:55 ` Patrick Steinhardt
0 siblings, 0 replies; 15+ messages in thread
From: Patrick Steinhardt @ 2026-10-27 14:55 UTC (permalink / raw)
To: Mark C. Chu-Carroll; +Cc: git
On Mon, Oct 26, 2026 at 01:43:55PM -0400, Mark C. Chu-Carroll wrote:
> diff --git a/builtin/repo.c b/builtin/repo.c
> index a450497d4e..ed579e6685 100644
> --- a/builtin/repo.c
> +++ b/builtin/repo.c
> @@ -386,40 +389,43 @@ enum concern_stat {
> CONCERN_STAT_TREE_INFLATED_SIZE,
> CONCERN_STAT_BLOB_INFLATED_SIZE,
> CONCERN_STAT_TAG_INFLATED_SIZE,
> + CONCERN_STAT_MAXIMUM
> };
>
> -struct concern_stat_threshold repo_structure_concern_thresholds[] = {
> - { .name = "commit_count", .threshold = 19 },
> - {.name = "tree_count", .threshold = 21 },
> - { .name = "blob_count", .threshold = 22 },
> - { .name = "tag_count", .threshold = 15 },
> - { .name = "commit_size", .threshold = 28 },
> - { .name = "tree_size", .threshold = 27 },
> - { .name = "blob_size", .threshold = 36 },
> - { .name = "tags_size", .threshold = 18 },
> - { .name = "commit_largest", .threshold = 18 },
> - { .name = "tree_largest", .threshold = 18 },
> - { .name = "blob_largest", .threshold = 26 },
> - { .name = "tag_largest", .threshold = 15 },
> - { .name = "commit_inflated_size", .threshold = 28 },
> - { .name = "tree_inflated_size", .threshold = 32 },
> - { .name = "blob_inflated_size", .threshold = 34 },
> - { .name = "tag_inflated_size", .threshold = 25}
> +struct concern_stat_threshold concern_stat_thresholds[] = {
> + { .name = "commit-count", .threshold = 19 },
> + { .name = "tree-count", .threshold = 21 },
> + { .name = "blob-count", .threshold = 22 },
> + { .name = "tag-count", .threshold = 15 },
> + { .name = "commit-size", .threshold = 28 },
> + { .name = "tree-size", .threshold = 27 },
> + { .name = "blob-size", .threshold = 36 },
> + { .name = "tag-size", .threshold = 18 },
> + { .name = "commit-largest", .threshold = 18 },
> + { .name = "tree-largest", .threshold = 18 },
> + { .name = "blob-largest", .threshold = 26 },
> + { .name = "tag-largest", .threshold = 15 },
> + { .name = "commit-inflated-size", .threshold = 28 },
> + { .name = "tree-inflated-size", .threshold = 32 },
> + { .name = "blob-inflated-size", .threshold = 34 },
> + { .name = "tag-inflated-size", .threshold = 25},
> + { 0 }
> };
This commit seems to fix up a couple of issues introduced in the
preceding commit. We generally require that every patch of a patch
series should compile and pass all tests so that we can bisect our
history.
Patrick
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2 3/3] repo: add documentation for level-of-concern.
2026-10-26 17:43 ` [PATCH v2 3/3] repo: add documentation for level-of-concern Mark C. Chu-Carroll
@ 2026-10-27 14:55 ` Patrick Steinhardt
2026-10-28 13:23 ` Mark C. Chu-Carroll
0 siblings, 1 reply; 15+ messages in thread
From: Patrick Steinhardt @ 2026-10-27 14:55 UTC (permalink / raw)
To: Mark C. Chu-Carroll; +Cc: git
On Mon, Oct 26, 2026 at 01:43:56PM -0400, Mark C. Chu-Carroll wrote:
> ---
> Documentation/config/repo.adoc | 59 ++++++++++++++++++++++++++++++++++++++++++
> Documentation/git-repo.adoc | 27 +++++++++++++++++--
> 2 files changed, 84 insertions(+), 2 deletions(-)
>
> diff --git a/Documentation/config/repo.adoc b/Documentation/config/repo.adoc
> new file mode 100644
> index 0000000000..811b099d72
> --- /dev/null
> +++ b/Documentation/config/repo.adoc
> @@ -0,0 +1,59 @@
> +`repo.concern.commit-count`::
> + Sets the value of the concern threshold for commit count.
> +
> +`repo.concern.tree-count`::
> + Sets the value of the concern threshold for tree count.
I think this commit can be split and squashed into the other two
commits:
- The change to synopsis and documentation of "--concern" should be
squashed into the first commit.
- These config values here should be squashed into the second commit.
Thanks!
Patrick
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v3 0/2] repo: add degree-of-concern reporting to structure
2026-10-21 13:55 [PATCH 0/3] repo: add degree-of-concern reporting to structure Mark C. Chu-Carroll
` (3 preceding siblings ...)
2026-10-26 17:43 ` [PATCH v2 0/3] repo: add degree-of-concern reporting to structure Mark C. Chu-Carroll
@ 2026-10-27 15:39 ` Mark C. Chu-Carroll
2026-10-27 15:39 ` [PATCH v3 1/2] repo: add level of concern reporting Mark C. Chu-Carroll
2026-10-27 15:39 ` [PATCH v3 2/2] repo: make concern thresholds configurable Mark C. Chu-Carroll
4 siblings, 2 replies; 15+ messages in thread
From: Mark C. Chu-Carroll @ 2026-10-27 15:39 UTC (permalink / raw)
To: git; +Cc: Mark C. Chu-Carroll
"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.
To select defaults for the thresholds where a value becomes concern,
a (hopefully) representative selection of repositories were analyzed.
Based on empirical exploration of the data, the measurement that seemed
closest to accurately assessing when a repository's structure becomes
concerning was based on the base 2 logarithm of the value. Across all of
the sample repositories, for all of the measurements, the point at which
things become problematic was when a base 2 logarithm of a value was
more than 1 standard deviation from the mean of the log_2 values in
the sample set. 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 the short term, projects
may want to set their own thresholds. So this patchset includes support
for configuring the thresholds on a per-reposiry basis.
Overview of the change:
* The first commit adds the command-line option "--concern", and creates the basic implementation of concern reporting.
* The next commit adds configurability for the concern thresholds.
Signed-off-by: Mark C. Chu-Carroll <markchucarroll@fastmail.com>
---
Changes in 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
---
Mark C. Chu-Carroll (2):
repo: add level of concern reporting
repo: make concern thresholds configurable
Documentation/config/repo.adoc | 59 +++++++
Documentation/git-repo.adoc | 27 +++-
builtin/repo.c | 343 ++++++++++++++++++++++++++++++++++++++---
t/t1901-repo-structure.sh | 137 ++++++++++++++++
4 files changed, 543 insertions(+), 23 deletions(-)
Range-diff versus v2:
1: b1ef56dd47 ! 1: 11c60f6d6d repo: add level of concern reporting
@@ Commit message
when a stat values has reached a point where it might be
concerning, and indicate how severe the concern should be.
+ ## Documentation/git-repo.adoc ##
+@@ Documentation/git-repo.adoc: SYNOPSIS
+ [synopsis]
+ git repo info [--format=(lines|nul) | -z] [--all | <key>...]
+ git repo info --keys [--format=(lines|nul) | -z]
+-git repo structure [--format=(table|lines|nul) | -z]
++git repo structure [--format=(table|lines|nul) | -z] [--[no-]concern]
+
+ DESCRIPTION
+ -----------
+@@ Documentation/git-repo.adoc: supported:
+ `nul`:::
+ Similar to `lines`, but using a _NUL_ character after each value.
+
+-`structure [--format=(table|lines|nul) | -z]`::
++`structure [--format=(table|lines|nul) | -z] [--[no-]concern]`::
+ Retrieve statistics about the current repository structure. The
+ following kinds of information are reported:
+ +
+@@ Documentation/git-repo.adoc: supported:
+ quoted.
+ +
+ `-z` is an alias for `--format=nul`.
+++
++To make it easier to know when one of the values is something that
++users should be concerned about, adding '--concern' will provide a
++level of concern to be added to the output. In 'table' mode, this will
++be a table column indicating the level of concern as a number of stars;
++in `lines` or `nul` mode, a `concern_level` line will be added for each
++statistic with a numeric value.
+++
++The level of concern for each statistic is computed using the log_2 of the
++statistic value. The value is considered concerning if it exceeds a heuristically
++chosen threshold value; the degree of concern is the difference between the
++log_2 of the value, and the log_2 of the corresponding threshold.
+
+ INFO KEYS
+ ---------
+
## builtin/repo.c ##
@@
"git repo info --keys [--format=(lines|nul) | -z]"
2: 446391b362 ! 2: 4eba5072b0 repo: make concern thresholds configurable
@@ Commit message
configure per-repo concern levels for "git repo structure"
concern reporting.
+ ## Documentation/config/repo.adoc (new) ##
+@@
++`repo.concern.commit-count`::
++ Sets the value of the concern threshold for commit count.
++
++`repo.concern.tree-count`::
++ Sets the value of the concern threshold for tree count.
++
++`repo.concern.blob-count`::
++ Sets the value of the concern threshold for blob count.
++
++`repo.concern.tag-count`::
++ Sets the value of the concern threshold for tag count.
++
++`repo.concern.commit-size`::
++ Sets the value of the concern threshold for total commit size.
++
++`repo.concern.tree-size`::
++ Sets the value of the concern threshold for total tree size.
++
++`repo.concern.blob-size`::
++ Sets the value of the concern threshold for total blob size.
++
++`repo.concern.tag-size`::
++ Sets the value of the concern threshold for total tag size.
++
++`repo.concern.commit-inflated-size`::
++ Sets the value of the concern threshold for total size
++ of all inflated commits.
++
++`repo.concern.tree-inflated-size`::
++ Sets the value of the concern threshold for total size of
++ all inflated trees.
++
++`repo.concern.blob-inflated-size`::
++ Sets the value of the concern threshold for total size of
++ all inflated blobs.
++
++`repo.concern.tag-inflated-size`::
++ Sets the value of the concern threshold for the total size
++ of all inflated tags.
++
++`repo.concern.commit-largest`::
++ Sets the value of the concern threshold the size of the largest
++ commit.
++
++`repo.concern.tree-largest`::
++ Sets the value of the concern threshold for the size of the
++ largest tree.
++
++`repo.concern.blob-largest`::
++ Sets the value of the concern threshold for the size of the
++ largest blob.
++
++`repo.concern.tag-largest`::
++ 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.
+
+ ## Documentation/git-repo.adoc ##
+@@ Documentation/git-repo.adoc: statistic value. The value is considered concerning if it exceeds a heuristicall
+ chosen threshold value; the degree of concern is the difference between the
+ log_2 of the value, and the log_2 of the corresponding threshold.
+
++The threshold values are configurable; see the configuration section below.
++
+ INFO KEYS
+ ---------
+ In order to obtain a set of values from `git repo info`, you should provide
+@@ Documentation/git-repo.adoc: using the `nul` format:
+ git repo info --format=nul layout.bare layout.shallow
+ ------------
+
++CONFIGURATION
++-------------
++
++include::includes/cmd-config-section-all.adoc[]
++
++:git-repo: 1
++include::config/repo.adoc[]
++
++
+ SEE ALSO
+ --------
+ linkgit:git-rev-parse[1]
+
## builtin/repo.c ##
@@
3: 92f1fafbcd < -: ---------- repo: add documentation for level-of-concern.
---
base-commit: 3bc0341126508f78f5869cbfc0005e987efdf0c7
change-id: 20261019-b4-repo-structure-concern-f1d6f6326095
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v3 1/2] repo: add level of concern reporting
2026-10-27 15:39 ` [PATCH v3 0/2] repo: add degree-of-concern reporting to structure Mark C. Chu-Carroll
@ 2026-10-27 15:39 ` Mark C. Chu-Carroll
2026-10-27 15:39 ` [PATCH v3 2/2] repo: make concern thresholds configurable Mark C. Chu-Carroll
1 sibling, 0 replies; 15+ messages in thread
From: Mark C. Chu-Carroll @ 2026-10-27 15:39 UTC (permalink / raw)
To: git; +Cc: Mark C. Chu-Carroll
"git repo structure" computes a set of statistics
that are useful for diagnosing performance issues in
a git repository. But for a typical user who hasn't
encountered any issues yet, it can be difficult to
interpret the data - when should a value be considered
concerning?
Add an option to "git repo structure" that adds a "degree
of concern" column to the structure report, which indicates
when a stat values has reached a point where it might be
concerning, and indicate how severe the concern should be.
---
Documentation/git-repo.adoc | 16 ++-
builtin/repo.c | 329 +++++++++++++++++++++++++++++++++++++++++---
sideband.c | 2 +-
t/t1901-repo-structure.sh | 125 ++++++++---------
4 files changed, 380 insertions(+), 92 deletions(-)
diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc
index ed7d80c690..f186b27d87 100644
--- a/Documentation/git-repo.adoc
+++ b/Documentation/git-repo.adoc
@@ -10,7 +10,7 @@ SYNOPSIS
[synopsis]
git repo info [--format=(lines|nul) | -z] [--all | <key>...]
git repo info --keys [--format=(lines|nul) | -z]
-git repo structure [--format=(table|lines|nul) | -z]
+git repo structure [--format=(table|lines|nul) | -z] [--[no-]concern]
DESCRIPTION
-----------
@@ -56,7 +56,7 @@ supported:
`nul`:::
Similar to `lines`, but using a _NUL_ character after each value.
-`structure [--format=(table|lines|nul) | -z]`::
+`structure [--format=(table|lines|nul) | -z] [--[no-]concern]`::
Retrieve statistics about the current repository structure. The
following kinds of information are reported:
+
@@ -88,6 +88,18 @@ supported:
quoted.
+
`-z` is an alias for `--format=nul`.
++
+To make it easier to know when one of the values is something that
+users should be concerned about, adding '--concern' will provide a
+level of concern to be added to the output. In 'table' mode, this will
+be a table column indicating the level of concern as a number of stars;
+in `lines` or `nul` mode, a `concern_level` line will be added for each
+statistic with a numeric value.
++
+The level of concern for each statistic is computed using the log_2 of the
+statistic value. The value is considered concerning if it exceeds a heuristically
+chosen threshold value; the degree of concern is the difference between the
+log_2 of the value, and the log_2 of the corresponding threshold.
INFO KEYS
---------
diff --git a/builtin/repo.c b/builtin/repo.c
index 84e012f83f..a450497d4e 100644
--- a/builtin/repo.c
+++ b/builtin/repo.c
@@ -27,7 +27,7 @@
"git repo info --keys [--format=(lines|nul) | -z]"
#define REPO_STRUCTURE_USAGE \
- "git repo structure [--format=(table|lines|nul) | -z]"
+ "git repo structure [--format=(table|lines|nul) | -z] [--[no-]concern]"
static const char *const repo_usage[] = {
REPO_INFO_USAGE,
@@ -348,8 +348,118 @@ struct stats_table {
int name_col_width;
int value_col_width;
int unit_col_width;
+ int concern_col_width;
};
+/*
+ * For computing level of concern about a stat, we need to have some
+ * baseline values. For each of these, we'define a minimum level
+ * of concern, which will appear as 1 star. Then we scale exponentially:
+ * each time that value doubles, we add another star.
+ *
+ * The threshold values for concern are expressed as log_2 values of the size
+ * at which the statistic becomes concerning. The default values for the
+ * threshold were selected based on observed values from a collection of
+ * representative-ish repositories of varying size, by finding the mean log_2
+ * value and standard deviation (sigma) for each statistic over the collection.
+ * The threshold for each metric is then set at mean(log_2) + sigma.
+ */
+struct concern_stat_threshold {
+ const char *name;
+ size_t threshold;
+};
+
+enum concern_stat {
+ CONCERN_STAT_COMMIT_COUNT,
+ CONCERN_STAT_TREE_COUNT,
+ CONCERN_STAT_BLOB_COUNT,
+ CONCERN_STAT_TAG_COUNT,
+ CONCERN_STAT_COMMIT_SIZE,
+ CONCERN_STAT_TREE_SIZE,
+ CONCERN_STAT_BLOB_SIZE,
+ CONCERN_STAT_TAG_SIZE,
+ CONCERN_STAT_COMMIT_LARGEST,
+ CONCERN_STAT_TREE_LARGEST,
+ CONCERN_STAT_BLOB_LARGEST,
+ CONCERN_STAT_TAG_LARGEST,
+ CONCERN_STAT_COMMIT_INFLATED_SIZE,
+ CONCERN_STAT_TREE_INFLATED_SIZE,
+ CONCERN_STAT_BLOB_INFLATED_SIZE,
+ CONCERN_STAT_TAG_INFLATED_SIZE,
+};
+
+struct concern_stat_threshold repo_structure_concern_thresholds[] = {
+ { .name = "commit_count", .threshold = 19 },
+ {.name = "tree_count", .threshold = 21 },
+ { .name = "blob_count", .threshold = 22 },
+ { .name = "tag_count", .threshold = 15 },
+ { .name = "commit_size", .threshold = 28 },
+ { .name = "tree_size", .threshold = 27 },
+ { .name = "blob_size", .threshold = 36 },
+ { .name = "tags_size", .threshold = 18 },
+ { .name = "commit_largest", .threshold = 18 },
+ { .name = "tree_largest", .threshold = 18 },
+ { .name = "blob_largest", .threshold = 26 },
+ { .name = "tag_largest", .threshold = 15 },
+ { .name = "commit_inflated_size", .threshold = 28 },
+ { .name = "tree_inflated_size", .threshold = 32 },
+ { .name = "blob_inflated_size", .threshold = 34 },
+ { .name = "tag_inflated_size", .threshold = 25}
+};
+
+/*
+ * Concern thresholds aren't universal. We try to set reasonable default
+ * values, but projects may want to set these thresholds differently. To
+ * support that, they can set the thresholds for their project in the
+ * project's git config, in the section "repo.structure.concern".
+ */
+static int config_concern_thresholds(const char *prefixed_var,
+ const char *str_val,
+ const struct config_context *ctx,
+ void *cb UNUSED) {
+ const char *config_prefix = "repo.structure.concern.";
+ if (!strncmp(config_prefix, prefixed_var, strlen(config_prefix))) {
+ const char *concern_name = &prefixed_var[strlen(config_prefix)];
+ for (int i = 0; concern_stat_thresholds[i].name != 0; i++) {
+ if (!strcmp(concern_stat_thresholds[i].name, concern_name)) {
+ int value = git_config_int(prefixed_var, str_val, ctx->kvi);
+ if (value < 0)
+ return -1;
+ concern_stat_thresholds[i].threshold = value;
+ return 0;
+ }
+ }
+ warning(_("Invalid concern threshold name: '%s'"), concern_name);
+ return 0;
+ }
+ return 0;
+}
+
+static size_t approx_log2(long value) {
+ int position = 0;
+ if (value <= 0) {
+ return 0;
+ }
+ while (value > 1) {
+ position++;
+ value >>= 1;
+ }
+ return position;
+}
+
+static size_t repo_structure_concern_level(enum concern_stat stat_id,
+ size_t value) {
+ size_t log_value;
+ if (stat_id >= ARRAY_SIZE(concern_stat_thresholds))
+ BUG("unexpected repository stat type");
+ log_value = approx_log2(value);
+ if (log_value > concern_stat_thresholds[stat_id].threshold)
+ return log_value -
+ concern_stat_thresholds[stat_id].threshold;
+ else
+ return 0;
+}
+
/*
* Holds column data that gets stored for each row.
*/
@@ -358,6 +468,7 @@ struct stats_table_entry {
const char *unit;
size_t index;
struct object_id *oid;
+ size_t concern;
};
static void stats_table_vaddf(struct stats_table *table,
@@ -391,6 +502,8 @@ 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->unit) {
int unit_width = utf8_strwidth(entry->unit);
if (unit_width > table->unit_col_width)
@@ -410,13 +523,14 @@ static void stats_table_addf(struct stats_table *table, const char *format, ...)
}
static void stats_table_count_addf(struct stats_table *table, size_t value,
- const char *format, ...)
+ size_t concern, const char *format, ...)
{
struct stats_table_entry *entry;
va_list ap;
CALLOC_ARRAY(entry, 1);
humanise_count(value, &entry->value, &entry->unit);
+ entry->concern = concern;
va_start(ap, format);
stats_table_vaddf(table, entry, format, ap);
@@ -425,13 +539,14 @@ static void stats_table_count_addf(struct stats_table *table, size_t value,
static void stats_table_object_count_addf(struct stats_table *table,
struct object_id *oid, size_t value,
- const char *format, ...)
+ size_t concern, const char *format, ...)
{
struct stats_table_entry *entry;
va_list ap;
CALLOC_ARRAY(entry, 1);
humanise_count(value, &entry->value, &entry->unit);
+ entry->concern = concern;
/*
* A NULL OID should not have a table annotation.
@@ -445,6 +560,7 @@ static void stats_table_object_count_addf(struct stats_table *table,
}
static void stats_table_size_addf(struct stats_table *table, size_t value,
+ size_t concern,
const char *format, ...)
{
struct stats_table_entry *entry;
@@ -452,6 +568,7 @@ static void stats_table_size_addf(struct stats_table *table, size_t value,
CALLOC_ARRAY(entry, 1);
humanise_bytes(value, &entry->value, &entry->unit, HUMANISE_COMPACT);
+ entry->concern = concern;
va_start(ap, format);
stats_table_vaddf(table, entry, format, ap);
@@ -460,6 +577,7 @@ static void stats_table_size_addf(struct stats_table *table, size_t value,
static void stats_table_object_size_addf(struct stats_table *table,
struct object_id *oid, size_t value,
+ size_t concern,
const char *format, ...)
{
struct stats_table_entry *entry;
@@ -467,6 +585,7 @@ static void stats_table_object_size_addf(struct stats_table *table,
CALLOC_ARRAY(entry, 1);
humanise_bytes(value, &entry->value, &entry->unit, HUMANISE_COMPACT);
+ entry->concern = concern;
/*
* A NULL OID should not have a table annotation.
@@ -501,47 +620,92 @@ static void stats_table_setup_structure(struct stats_table *table,
ref_total = get_total_reference_count(refs);
stats_table_addf(table, "* %s", _("References"));
- stats_table_count_addf(table, ref_total, " * %s", _("Count"));
- stats_table_count_addf(table, refs->branches, " * %s", _("Branches"));
- stats_table_count_addf(table, refs->tags, " * %s", _("Tags"));
- stats_table_count_addf(table, refs->remotes, " * %s", _("Remotes"));
- stats_table_count_addf(table, refs->others, " * %s", _("Others"));
+ stats_table_count_addf(table, ref_total, 0,
+ " * %s", _("Count"));
+ stats_table_count_addf(table, refs->branches, 0,
+ " * %s", _("Branches"));
+ stats_table_count_addf(table, refs->tags,
+ repo_structure_concern_level(
+ CONCERN_STAT_TAG_COUNT,
+ refs->tags),
+ " * %s", _("Tags"));
+ stats_table_count_addf(table, refs->remotes, 0,
+ " * %s", _("Remotes"));
+ stats_table_count_addf(table, refs->others, 0,
+ " * %s", _("Others"));
object_count_total = get_total_object_values(&objects->type_counts);
stats_table_addf(table, "");
stats_table_addf(table, "* %s", _("Reachable objects"));
- stats_table_count_addf(table, object_count_total, " * %s", _("Count"));
+ stats_table_count_addf(table, object_count_total, 0,
+ " * %s", _("Count"));
stats_table_count_addf(table, objects->type_counts.commits,
+ repo_structure_concern_level(
+ CONCERN_STAT_COMMIT_COUNT,
+ objects->type_counts.commits),
" * %s", _("Commits"));
stats_table_count_addf(table, objects->type_counts.trees,
+ repo_structure_concern_level(
+ CONCERN_STAT_TREE_COUNT,
+ objects->type_counts.trees),
" * %s", _("Trees"));
stats_table_count_addf(table, objects->type_counts.blobs,
+ repo_structure_concern_level(
+ CONCERN_STAT_BLOB_COUNT,
+ objects->type_counts.blobs),
" * %s", _("Blobs"));
stats_table_count_addf(table, objects->type_counts.tags,
+ repo_structure_concern_level(
+ CONCERN_STAT_TAG_COUNT,
+ objects->type_counts.tags),
" * %s", _("Tags"));
inflated_object_total = get_total_object_values(&objects->inflated_sizes);
- stats_table_size_addf(table, inflated_object_total,
+ stats_table_size_addf(table, inflated_object_total, 0,
" * %s", _("Inflated size"));
stats_table_size_addf(table, objects->inflated_sizes.commits,
+ repo_structure_concern_level(
+ CONCERN_STAT_COMMIT_INFLATED_SIZE,
+ objects->inflated_sizes.commits),
" * %s", _("Commits"));
stats_table_size_addf(table, objects->inflated_sizes.trees,
+ repo_structure_concern_level(
+ CONCERN_STAT_TREE_INFLATED_SIZE,
+ objects->inflated_sizes.trees),
" * %s", _("Trees"));
stats_table_size_addf(table, objects->inflated_sizes.blobs,
+ repo_structure_concern_level(
+ CONCERN_STAT_BLOB_INFLATED_SIZE,
+ objects->inflated_sizes.blobs),
" * %s", _("Blobs"));
stats_table_size_addf(table, objects->inflated_sizes.tags,
+ repo_structure_concern_level(
+ CONCERN_STAT_TAG_INFLATED_SIZE,
+ objects->inflated_sizes.tags),
" * %s", _("Tags"));
disk_object_total = get_total_object_values(&objects->disk_sizes);
- stats_table_size_addf(table, disk_object_total,
+ stats_table_size_addf(table, disk_object_total, 0,
" * %s", _("Disk size"));
stats_table_size_addf(table, objects->disk_sizes.commits,
+ repo_structure_concern_level(
+ CONCERN_STAT_COMMIT_SIZE,
+ objects->disk_sizes.commits),
" * %s", _("Commits"));
stats_table_size_addf(table, objects->disk_sizes.trees,
+ repo_structure_concern_level(
+ CONCERN_STAT_TREE_SIZE,
+ objects->disk_sizes.trees),
" * %s", _("Trees"));
stats_table_size_addf(table, objects->disk_sizes.blobs,
+ repo_structure_concern_level(
+ CONCERN_STAT_BLOB_SIZE,
+ objects->disk_sizes.blobs),
" * %s", _("Blobs"));
stats_table_size_addf(table, objects->disk_sizes.tags,
+ repo_structure_concern_level(
+ CONCERN_STAT_TAG_SIZE,
+ objects->disk_sizes.tags),
" * %s", _("Tags"));
stats_table_addf(table, "");
@@ -550,51 +714,71 @@ static void stats_table_setup_structure(struct stats_table *table,
stats_table_object_size_addf(table,
&objects->largest.commit_size.oid,
objects->largest.commit_size.value,
+ repo_structure_concern_level(
+ CONCERN_STAT_COMMIT_LARGEST,
+ objects->largest.commit_size.value),
" * %s", _("Maximum size"));
stats_table_object_count_addf(table,
&objects->largest.parent_count.oid,
objects->largest.parent_count.value,
+ 0,
" * %s", _("Maximum parents"));
stats_table_addf(table, " * %s", _("Trees"));
stats_table_object_size_addf(table,
&objects->largest.tree_size.oid,
objects->largest.tree_size.value,
+ repo_structure_concern_level(
+ CONCERN_STAT_TREE_LARGEST,
+ objects->largest.tree_size.value),
" * %s", _("Maximum size"));
stats_table_object_count_addf(table,
&objects->largest.tree_entries.oid,
objects->largest.tree_entries.value,
+ 0,
" * %s", _("Maximum entries"));
stats_table_addf(table, " * %s", _("Blobs"));
stats_table_object_size_addf(table,
&objects->largest.blob_size.oid,
objects->largest.blob_size.value,
+ repo_structure_concern_level(
+ CONCERN_STAT_BLOB_LARGEST,
+ objects->largest.blob_size.value),
" * %s", _("Maximum size"));
stats_table_addf(table, " * %s", _("Tags"));
stats_table_object_size_addf(table,
&objects->largest.tag_size.oid,
objects->largest.tag_size.value,
+ repo_structure_concern_level(
+ CONCERN_STAT_TAG_LARGEST,
+ objects->largest.tag_size.value),
" * %s", _("Maximum size"));
}
#define INDEX_WIDTH 4
-static void stats_table_print_structure(const struct stats_table *table)
+static void stats_table_print_structure(const struct stats_table *table,
+ bool show_concern)
{
const char *name_col_title = _("Repository structure");
const char *value_col_title = _("Value");
+ 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);
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;
struct string_list_item *item;
struct strbuf buf = STRBUF_INIT;
+ struct strbuf concern_str = STRBUF_INIT;
if (title_name_width > name_col_width)
name_col_width = title_name_width;
if (title_value_width > value_col_width + unit_col_width + 1)
value_col_width = title_value_width - unit_col_width;
-
+ 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);
@@ -602,6 +786,12 @@ static void stats_table_print_structure(const struct stats_table *table)
strbuf_utf8_align(&buf, ALIGN_LEFT,
value_col_width + unit_col_width + 1, value_col_title);
strbuf_addstr(&buf, " |");
+ if (show_concern) {
+ strbuf_addch(&buf, ' ');
+ strbuf_utf8_align(&buf, ALIGN_LEFT, concern_col_width,
+ concern_col_title);
+ strbuf_addstr(&buf, " |");
+ }
printf("%s\n", buf.buf);
printf("| ");
@@ -610,17 +800,28 @@ static void stats_table_print_structure(const struct stats_table *table)
printf(" | ");
for (int i = 0; i < value_col_width + unit_col_width + 1; i++)
putchar('-');
- printf(" |\n");
+ printf(" |");
+ if (show_concern) {
+ putchar(' ');
+ for (int i = 0; i < concern_col_width; i++) {
+ putchar('-');
+ }
+ printf(" |");
+ }
+ putchar('\n');
for_each_string_list_item(item, &table->rows) {
struct stats_table_entry *entry = item->util;
const char *value = "";
const char *unit = "";
+ strbuf_reset(&concern_str);
if (entry) {
value = entry->value;
if (entry->unit)
unit = entry->unit;
+ if (entry->concern > 0)
+ strbuf_addchars(&concern_str, '*', entry->concern);
}
strbuf_reset(&buf);
@@ -638,6 +839,11 @@ static void stats_table_print_structure(const struct stats_table *table)
strbuf_addch(&buf, ' ');
strbuf_utf8_align(&buf, ALIGN_LEFT, unit_col_width, unit);
strbuf_addstr(&buf, " |");
+ if (show_concern) {
+ strbuf_addch(&buf, ' ');
+ strbuf_utf8_align(&buf, ALIGN_LEFT, concern_col_width, concern_str.buf);
+ strbuf_addstr(&buf, " |");
+ }
printf("%s\n", buf.buf);
}
@@ -681,12 +887,15 @@ static void print_object_data(const char *key, char key_delim,
}
static void structure_keyvalue_print(struct repo_structure *stats,
- char key_delim, char value_delim)
+ char key_delim, char value_delim,
+ bool show_concern)
{
print_keyvalue("references.branches.count", key_delim,
stats->refs.branches, value_delim);
+
print_keyvalue("references.tags.count", key_delim,
stats->refs.tags, value_delim);
+
print_keyvalue("references.remotes.count", key_delim,
stats->refs.remotes, value_delim);
print_keyvalue("references.others.count", key_delim,
@@ -694,39 +903,120 @@ static void structure_keyvalue_print(struct repo_structure *stats,
print_keyvalue("objects.commits.count", key_delim,
stats->objects.type_counts.commits, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.commits.count.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_COMMIT_COUNT,
+ stats->objects.type_counts.commits),
+ value_delim);
print_keyvalue("objects.trees.count", key_delim,
stats->objects.type_counts.trees, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.trees.count.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_TREE_COUNT,
+ stats->objects.type_counts.trees),
+ value_delim);
print_keyvalue("objects.blobs.count", key_delim,
stats->objects.type_counts.blobs, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.blobs.count.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_BLOB_COUNT,
+ stats->objects.type_counts.blobs),
+ value_delim);
print_keyvalue("objects.tags.count", key_delim,
stats->objects.type_counts.tags, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.tags.count.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_TAG_COUNT,
+ stats->objects.type_counts.tags),
+ value_delim);
print_keyvalue("objects.commits.inflated_size", key_delim,
stats->objects.inflated_sizes.commits, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.commits.inflated_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_COMMIT_INFLATED_SIZE,
+ stats->objects.inflated_sizes.commits),
+ value_delim);
print_keyvalue("objects.trees.inflated_size", key_delim,
stats->objects.inflated_sizes.trees, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.trees.inflated_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_TREE_INFLATED_SIZE,
+ stats->objects.inflated_sizes.trees),
+ value_delim);
+
print_keyvalue("objects.blobs.inflated_size", key_delim,
stats->objects.inflated_sizes.blobs, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.blobs.inflated_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_BLOB_INFLATED_SIZE,
+ stats->objects.inflated_sizes.blobs),
+ value_delim);
print_keyvalue("objects.tags.inflated_size", key_delim,
stats->objects.inflated_sizes.tags, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.tags.inflated_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_TAG_INFLATED_SIZE,
+ stats->objects.inflated_sizes.tags),
+ value_delim);
print_keyvalue("objects.commits.disk_size", key_delim,
stats->objects.disk_sizes.commits, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.commits.disk_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_COMMIT_SIZE,
+ stats->objects.disk_sizes.commits),
+ value_delim);
print_keyvalue("objects.trees.disk_size", key_delim,
stats->objects.disk_sizes.trees, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.trees.disk_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_TREE_SIZE,
+ stats->objects.disk_sizes.trees),
+ value_delim);
print_keyvalue("objects.blobs.disk_size", key_delim,
stats->objects.disk_sizes.blobs, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.blobs.blob_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_BLOB_SIZE,
+ stats->objects.disk_sizes.blobs),
+ 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),
+ value_delim);
print_object_data("objects.commits.max_size", key_delim,
&stats->objects.largest.commit_size, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.commits.max_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_COMMIT_LARGEST,
+ stats->objects.largest.commit_size.value),
+ value_delim);
print_object_data("objects.trees.max_size", key_delim,
&stats->objects.largest.tree_size, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.trees.max_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_TREE_LARGEST,
+ stats->objects.largest.tree_size.value),
+ value_delim);
print_object_data("objects.blobs.max_size", key_delim,
&stats->objects.largest.blob_size, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.blobs.max_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_BLOB_LARGEST,
+ stats->objects.largest.blob_size.value),
+ value_delim);
print_object_data("objects.tags.max_size", key_delim,
&stats->objects.largest.tag_size, value_delim);
+ if (show_concern)
+ print_keyvalue("objects.tags.max_size.concern_level", key_delim,
+ repo_structure_concern_level(CONCERN_STAT_TAG_LARGEST,
+ stats->objects.largest.tag_size.value),
+ value_delim);
print_object_data("objects.commits.max_parents", key_delim,
&stats->objects.largest.parent_count, value_delim);
@@ -935,6 +1225,8 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
struct repo_structure stats = { 0 };
struct rev_info revs;
int show_progress = -1;
+ int show_concern = 0;
+
struct option options[] = {
OPT_CALLBACK_F(0, "format", &format, N_("format"),
N_("output format"),
@@ -944,6 +1236,7 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
PARSE_OPT_NONEG | PARSE_OPT_NOARG,
parse_format_cb),
OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
+ OPT_BOOL(0, "concern", &show_concern, N_("show level of concern")),
OPT_END()
};
@@ -962,13 +1255,13 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
switch (format) {
case FORMAT_TABLE:
stats_table_setup_structure(&table, &stats);
- stats_table_print_structure(&table);
+ stats_table_print_structure(&table, show_concern);
break;
case FORMAT_NEWLINE_TERMINATED:
- structure_keyvalue_print(&stats, '=', '\n');
+ structure_keyvalue_print(&stats, '=', '\n', show_concern);
break;
case FORMAT_NUL_TERMINATED:
- structure_keyvalue_print(&stats, '\n', '\0');
+ structure_keyvalue_print(&stats, '\n', '\0', show_concern);
break;
default:
BUG("invalid output format");
diff --git a/sideband.c b/sideband.c
index 374187402e..0e75ab2317 100644
--- a/sideband.c
+++ b/sideband.c
@@ -1,4 +1,4 @@
-#define USE_THE_REPOSITORY_VARIABLE
+define USE_THE_REPOSITORY_VARIABLE
#define DISABLE_SIGN_COMPARE_WARNINGS
#include "git-compat-util.h"
diff --git a/t/t1901-repo-structure.sh b/t/t1901-repo-structure.sh
index 02cc2b594a..f1e3fd2a75 100755
--- a/t/t1901-repo-structure.sh
+++ b/t/t1901-repo-structure.sh
@@ -67,77 +67,7 @@ test_expect_success 'empty repository' '
EOF
git repo structure >out 2>err &&
-
- test_cmp expect out &&
- test_line_count = 0 err
- )
-'
-
-test_expect_success SHA1 'repository with references and objects' '
- test_when_finished "rm -rf repo" &&
- git init repo &&
- (
- cd repo &&
- test_commit_bulk 1005 &&
- git tag -a foo -m bar &&
-
- oid="$(git rev-parse HEAD)" &&
- git update-ref refs/remotes/origin/foo "$oid" &&
-
- # Also creates a commit, tree, and blob.
- git notes add -m foo &&
-
- # The tags disk size is handled specially due to the
- # git-rev-list(1) --disk-usage=human option printing the full
- # "byte/bytes" unit string instead of just "B".
- cat >expect <<-EOF &&
- | Repository structure | Value |
- | ------------------------- | ---------- |
- | * References | |
- | * Count | 4 |
- | * Branches | 1 |
- | * Tags | 1 |
- | * Remotes | 1 |
- | * Others | 1 |
- | | |
- | * Reachable objects | |
- | * Count | 3.02 k |
- | * Commits | 1.01 k |
- | * Trees | 1.01 k |
- | * Blobs | 1.01 k |
- | * Tags | 1 |
- | * Inflated size | 16.03 MiB |
- | * Commits | 217.92 KiB |
- | * Trees | 15.81 MiB |
- | * Blobs | 11.68 KiB |
- | * Tags | 132 B |
- | * Disk size | $(object_type_disk_usage all true) |
- | * Commits | $(object_type_disk_usage commit true) |
- | * Trees | $(object_type_disk_usage tree true) |
- | * Blobs | $(object_type_disk_usage blob true) |
- | * Tags | $(object_type_disk_usage tag) B |
- | | |
- | * Largest objects | |
- | * Commits | |
- | * Maximum size [1] | 223 B |
- | * Maximum parents [2] | 1 |
- | * Trees | |
- | * Maximum size [3] | 32.29 KiB |
- | * Maximum entries [4] | 1.01 k |
- | * Blobs | |
- | * Maximum size [5] | 13 B |
- | * Tags | |
- | * Maximum size [6] | 132 B |
-
- [1] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a
- [2] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a
- [3] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c
- [4] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c
- [5] 97d808e45116bf02103490294d3d46dad7a2ac62
- [6] 4dae4f5954f5e6feb3577cfb1b181daa3fd3afd2
- EOF
-
- git repo structure >out 2>err &&
+ cp out /tmp/test1-out &&
test_cmp expect out &&
test_line_count = 0 err
@@ -204,6 +134,59 @@ test_expect_success SHA1 'lines and nul format' '
)
'
+test_expect_success SHA1 'lines format with concerns' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ # configure thresholds to get concern indications
+ git config repo.structure.concern.commit-count 7 &&
+ git config repo.structure.concern.commit-size 14 &&
+ git config repo.structure.concern.tree-size 10 &&
+ git config repo.structure.concern.blob-size 8 &&
+ git config repo.structure.concern.blob-inflated-size 10 &&
+ git config repo.structure.concern.tag-size 2 &&
+ git config repo.structure.concern.commit-largest 4 &&
+ git config repo.structure.concern.blob-largest 2 &&
+ touch configured &&
+ test_commit_bulk 1005 &&
+ git tag -a foo -m bar &&
+ oid="$(git rev-parse HEAD)" &&
+ git update-ref refs/remotes/origin/foo "$oid" &&
+
+ # Also creates a commit, tree, and blob.
+ git notes add -m foo &&
+
+
+ # The tags disk size is handled specially due to the
+ # git-rev-list(1) --disk-usage=human option printing the full
+ # "byte/bytes" unit string instead of just "B".
+ cat >expect <<-EOF &&
+ objects.commits.count.concern_level=2
+ objects.trees.count.concern_level=0
+ objects.blobs.count.concern_level=0
+ objects.tags.count.concern_level=0
+ objects.commits.inflated_size.concern_level=0
+ objects.trees.inflated_size.concern_level=0
+ objects.blobs.inflated_size.concern_level=3
+ objects.tags.inflated_size.concern_level=0
+ objects.commits.disk_size.concern_level=3
+ objects.trees.disk_size.concern_level=8
+ objects.blobs.blob_size.concern_level=6
+ objects.tags.disk_size.concern_level=4
+ objects.commits.max_size.concern_level=3
+ objects.trees.max_size.concern_level=0
+ objects.blobs.max_size.concern_level=1
+ objects.tags.max_size.concern_level=0
+ EOF
+
+ git repo structure --concern --format=lines >actual 2>err &&
+ test_grep "concern_level" actual >actual-concerns &&
+ test_cmp expect actual-concerns &&
+ test_line_count = 0 err
+ )
+'
+
test_expect_success 'progress meter option' '
test_when_finished "rm -rf repo" &&
git init repo &&
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v3 2/2] repo: make concern thresholds configurable
2026-10-27 15:39 ` [PATCH v3 0/2] repo: add degree-of-concern reporting to structure Mark C. Chu-Carroll
2026-10-27 15:39 ` [PATCH v3 1/2] repo: add level of concern reporting Mark C. Chu-Carroll
@ 2026-10-27 15:39 ` Mark C. Chu-Carroll
1 sibling, 0 replies; 15+ messages in thread
From: Mark C. Chu-Carroll @ 2026-10-27 15:39 UTC (permalink / raw)
To: git; +Cc: Mark C. Chu-Carroll
The concern reporting mechanism so far provides a
measure of concern based on heuristic values estimated
by analyzing a collection of (hopefully) representative
repositories. But for different projects/usecases, the
thresholds at which a value becomes concerning may vary.
Provide a mechanism using git config to allow users to
configure per-repo concern levels for "git repo structure"
concern reporting.
---
Documentation/config/repo.adoc | 59 ++++++++++++++
Documentation/git-repo.adoc | 11 +++
builtin/repo.c | 62 +++++++-------
sideband.c | 2 +-
t/t1901-repo-structure.sh | 180 ++++++++++++++++++++++++++++++++++++++---
5 files changed, 273 insertions(+), 41 deletions(-)
diff --git a/Documentation/config/repo.adoc b/Documentation/config/repo.adoc
new file mode 100644
index 0000000000..811b099d72
--- /dev/null
+++ b/Documentation/config/repo.adoc
@@ -0,0 +1,59 @@
+`repo.concern.commit-count`::
+ Sets the value of the concern threshold for commit count.
+
+`repo.concern.tree-count`::
+ Sets the value of the concern threshold for tree count.
+
+`repo.concern.blob-count`::
+ Sets the value of the concern threshold for blob count.
+
+`repo.concern.tag-count`::
+ Sets the value of the concern threshold for tag count.
+
+`repo.concern.commit-size`::
+ Sets the value of the concern threshold for total commit size.
+
+`repo.concern.tree-size`::
+ Sets the value of the concern threshold for total tree size.
+
+`repo.concern.blob-size`::
+ Sets the value of the concern threshold for total blob size.
+
+`repo.concern.tag-size`::
+ Sets the value of the concern threshold for total tag size.
+
+`repo.concern.commit-inflated-size`::
+ Sets the value of the concern threshold for total size
+ of all inflated commits.
+
+`repo.concern.tree-inflated-size`::
+ Sets the value of the concern threshold for total size of
+ all inflated trees.
+
+`repo.concern.blob-inflated-size`::
+ Sets the value of the concern threshold for total size of
+ all inflated blobs.
+
+`repo.concern.tag-inflated-size`::
+ Sets the value of the concern threshold for the total size
+ of all inflated tags.
+
+`repo.concern.commit-largest`::
+ Sets the value of the concern threshold the size of the largest
+ commit.
+
+`repo.concern.tree-largest`::
+ Sets the value of the concern threshold for the size of the
+ largest tree.
+
+`repo.concern.blob-largest`::
+ Sets the value of the concern threshold for the size of the
+ largest blob.
+
+`repo.concern.tag-largest`::
+ 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.
diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc
index f186b27d87..516403e17d 100644
--- a/Documentation/git-repo.adoc
+++ b/Documentation/git-repo.adoc
@@ -101,6 +101,8 @@ statistic value. The value is considered concerning if it exceeds a heuristicall
chosen threshold value; the degree of concern is the difference between the
log_2 of the value, and the log_2 of the corresponding threshold.
+The threshold values are configurable; see the configuration section below.
+
INFO KEYS
---------
In order to obtain a set of values from `git repo info`, you should provide
@@ -153,6 +155,15 @@ using the `nul` format:
git repo info --format=nul layout.bare layout.shallow
------------
+CONFIGURATION
+-------------
+
+include::includes/cmd-config-section-all.adoc[]
+
+:git-repo: 1
+include::config/repo.adoc[]
+
+
SEE ALSO
--------
linkgit:git-rev-parse[1]
diff --git a/builtin/repo.c b/builtin/repo.c
index a450497d4e..ed579e6685 100644
--- a/builtin/repo.c
+++ b/builtin/repo.c
@@ -2,6 +2,7 @@
#include "builtin.h"
#include "commit.h"
+#include "config.h"
#include "environment.h"
#include "hash.h"
#include "hex.h"
@@ -29,6 +30,8 @@
#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,
@@ -386,40 +389,43 @@ enum concern_stat {
CONCERN_STAT_TREE_INFLATED_SIZE,
CONCERN_STAT_BLOB_INFLATED_SIZE,
CONCERN_STAT_TAG_INFLATED_SIZE,
+ CONCERN_STAT_MAXIMUM
};
-struct concern_stat_threshold repo_structure_concern_thresholds[] = {
- { .name = "commit_count", .threshold = 19 },
- {.name = "tree_count", .threshold = 21 },
- { .name = "blob_count", .threshold = 22 },
- { .name = "tag_count", .threshold = 15 },
- { .name = "commit_size", .threshold = 28 },
- { .name = "tree_size", .threshold = 27 },
- { .name = "blob_size", .threshold = 36 },
- { .name = "tags_size", .threshold = 18 },
- { .name = "commit_largest", .threshold = 18 },
- { .name = "tree_largest", .threshold = 18 },
- { .name = "blob_largest", .threshold = 26 },
- { .name = "tag_largest", .threshold = 15 },
- { .name = "commit_inflated_size", .threshold = 28 },
- { .name = "tree_inflated_size", .threshold = 32 },
- { .name = "blob_inflated_size", .threshold = 34 },
- { .name = "tag_inflated_size", .threshold = 25}
+struct concern_stat_threshold concern_stat_thresholds[] = {
+ { .name = "commit-count", .threshold = 19 },
+ { .name = "tree-count", .threshold = 21 },
+ { .name = "blob-count", .threshold = 22 },
+ { .name = "tag-count", .threshold = 15 },
+ { .name = "commit-size", .threshold = 28 },
+ { .name = "tree-size", .threshold = 27 },
+ { .name = "blob-size", .threshold = 36 },
+ { .name = "tag-size", .threshold = 18 },
+ { .name = "commit-largest", .threshold = 18 },
+ { .name = "tree-largest", .threshold = 18 },
+ { .name = "blob-largest", .threshold = 26 },
+ { .name = "tag-largest", .threshold = 15 },
+ { .name = "commit-inflated-size", .threshold = 28 },
+ { .name = "tree-inflated-size", .threshold = 32 },
+ { .name = "blob-inflated-size", .threshold = 34 },
+ { .name = "tag-inflated-size", .threshold = 25},
+ { 0 }
};
/*
* Concern thresholds aren't universal. We try to set reasonable default
* values, but projects may want to set these thresholds differently. To
* support that, they can set the thresholds for their project in the
- * project's git config, in the section "repo.structure.concern".
+ * project's git config, in the section "repo.concern".
*/
static int config_concern_thresholds(const char *prefixed_var,
const char *str_val,
const struct config_context *ctx,
void *cb UNUSED) {
- const char *config_prefix = "repo.structure.concern.";
- if (!strncmp(config_prefix, prefixed_var, strlen(config_prefix))) {
- const char *concern_name = &prefixed_var[strlen(config_prefix)];
+ if (!strncmp(REPO_STRUCTURE_CONFIG_PREFIX, prefixed_var,
+ strlen(REPO_STRUCTURE_CONFIG_PREFIX))) {
+ const char *concern_name =
+ &prefixed_var[strlen(REPO_STRUCTURE_CONFIG_PREFIX)];
for (int i = 0; concern_stat_thresholds[i].name != 0; i++) {
if (!strcmp(concern_stat_thresholds[i].name, concern_name)) {
int value = git_config_int(prefixed_var, str_val, ctx->kvi);
@@ -435,7 +441,7 @@ static int config_concern_thresholds(const char *prefixed_var,
return 0;
}
-static size_t approx_log2(long value) {
+static size_t approx_log2(size_t value) {
int position = 0;
if (value <= 0) {
return 0;
@@ -450,7 +456,7 @@ static size_t approx_log2(long value) {
static size_t repo_structure_concern_level(enum concern_stat stat_id,
size_t value) {
size_t log_value;
- if (stat_id >= ARRAY_SIZE(concern_stat_thresholds))
+ if (stat_id >= CONCERN_STAT_MAXIMUM)
BUG("unexpected repository stat type");
log_value = approx_log2(value);
if (log_value > concern_stat_thresholds[stat_id].threshold)
@@ -844,6 +850,7 @@ 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);
}
@@ -977,7 +984,7 @@ static void structure_keyvalue_print(struct repo_structure *stats,
print_keyvalue("objects.blobs.disk_size", key_delim,
stats->objects.disk_sizes.blobs, value_delim);
if (show_concern)
- print_keyvalue("objects.blobs.blob_size.concern_level", key_delim,
+ print_keyvalue("objects.blobs.disk_size.concern_level", key_delim,
repo_structure_concern_level(CONCERN_STAT_BLOB_SIZE,
stats->objects.disk_sizes.blobs),
value_delim);
@@ -997,7 +1004,7 @@ static void structure_keyvalue_print(struct repo_structure *stats,
stats->objects.largest.commit_size.value),
value_delim);
print_object_data("objects.trees.max_size", key_delim,
- &stats->objects.largest.tree_size, value_delim);
+ &stats->objects.largest.tree_size, value_delim);
if (show_concern)
print_keyvalue("objects.trees.max_size.concern_level", key_delim,
repo_structure_concern_level(CONCERN_STAT_TREE_LARGEST,
@@ -1011,7 +1018,7 @@ static void structure_keyvalue_print(struct repo_structure *stats,
stats->objects.largest.blob_size.value),
value_delim);
print_object_data("objects.tags.max_size", key_delim,
- &stats->objects.largest.tag_size, value_delim);
+ &stats->objects.largest.tag_size, value_delim);
if (show_concern)
print_keyvalue("objects.tags.max_size.concern_level", key_delim,
repo_structure_concern_level(CONCERN_STAT_TAG_LARGEST,
@@ -1243,7 +1250,8 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
argc = parse_options(argc, argv, prefix, options, repo_structure_usage, 0);
if (argc)
usage(_("too many arguments"));
-
+ if (show_concern)
+ repo_config(repo, &config_concern_thresholds, NULL);
repo_init_revisions(repo, &revs, prefix);
if (show_progress < 0)
diff --git a/sideband.c b/sideband.c
index 0e75ab2317..374187402e 100644
--- a/sideband.c
+++ b/sideband.c
@@ -1,4 +1,4 @@
-define USE_THE_REPOSITORY_VARIABLE
+#define USE_THE_REPOSITORY_VARIABLE
#define DISABLE_SIGN_COMPARE_WARNINGS
#include "git-compat-util.h"
diff --git a/t/t1901-repo-structure.sh b/t/t1901-repo-structure.sh
index f1e3fd2a75..e8f1269ad4 100755
--- a/t/t1901-repo-structure.sh
+++ b/t/t1901-repo-structure.sh
@@ -67,13 +67,166 @@ 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
)
'
+test_expect_success SHA1 'repository with references and objects' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit_bulk 1005 &&
+ git tag -a foo -m bar &&
+
+ oid="$(git rev-parse HEAD)" &&
+ git update-ref refs/remotes/origin/foo "$oid" &&
+
+ # Also creates a commit, tree, and blob.
+ git notes add -m foo &&
+
+ # The tags disk size is handled specially due to the
+ # git-rev-list(1) --disk-usage=human option printing the full
+ # "byte/bytes" unit string instead of just "B".
+ cat >expect <<-EOF &&
+ | Repository structure | Value |
+ | ------------------------- | ---------- |
+ | * References | |
+ | * Count | 4 |
+ | * Branches | 1 |
+ | * Tags | 1 |
+ | * Remotes | 1 |
+ | * Others | 1 |
+ | | |
+ | * Reachable objects | |
+ | * Count | 3.02 k |
+ | * Commits | 1.01 k |
+ | * Trees | 1.01 k |
+ | * Blobs | 1.01 k |
+ | * Tags | 1 |
+ | * Inflated size | 16.03 MiB |
+ | * Commits | 217.92 KiB |
+ | * Trees | 15.81 MiB |
+ | * Blobs | 11.68 KiB |
+ | * Tags | 132 B |
+ | * Disk size | $(object_type_disk_usage all true) |
+ | * Commits | $(object_type_disk_usage commit true) |
+ | * Trees | $(object_type_disk_usage tree true) |
+ | * Blobs | $(object_type_disk_usage blob true) |
+ | * Tags | $(object_type_disk_usage tag) B |
+ | | |
+ | * Largest objects | |
+ | * Commits | |
+ | * Maximum size [1] | 223 B |
+ | * Maximum parents [2] | 1 |
+ | * Trees | |
+ | * Maximum size [3] | 32.29 KiB |
+ | * Maximum entries [4] | 1.01 k |
+ | * Blobs | |
+ | * Maximum size [5] | 13 B |
+ | * Tags | |
+ | * Maximum size [6] | 132 B |
+
+ [1] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a
+ [2] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a
+ [3] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c
+ [4] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c
+ [5] 97d808e45116bf02103490294d3d46dad7a2ac62
+ [6] 4dae4f5954f5e6feb3577cfb1b181daa3fd3afd2
+ EOF
+
+ git repo structure >out 2>err &&
+
+ test_cmp expect out &&
+ test_line_count = 0 err
+ )
+'
+
+test_expect_success SHA1 'concern flagging' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ # differences between zlib and zlib-ng can cause this
+ # 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 &&
+ test_commit_bulk 1005 &&
+ git tag -a foo -m bar &&
+ oid="$(git rev-parse HEAD)" &&
+ git update-ref refs/remotes/origin/foo "$oid" &&
+
+ # Also creates a commit, tree, and blob.
+ git notes add -m foo &&
+
+
+ # The tags disk size is handled specially due to the
+ # git-rev-list(1) --disk-usage=human option printing the full
+ # "byte/bytes" unit string instead of just "B".
+ cat >expect <<-EOF &&
+ | Repository structure | Value | Concern |
+ | ------------------------- | ---------- | -------- |
+ | * References | | |
+ | * Count | 4 | |
+ | * Branches | 1 | |
+ | * Tags | 1 | |
+ | * Remotes | 1 | |
+ | * Others | 1 | |
+ | | | |
+ | * Reachable objects | | |
+ | * Count | 3.02 k | |
+ | * Commits | 1.01 k | ** |
+ | * Trees | 1.01 k | |
+ | * Blobs | 1.01 k | |
+ | * Tags | 1 | |
+ | * Inflated size | 16.03 MiB | |
+ | * Commits | 217.92 KiB | |
+ | * Trees | 15.81 MiB | |
+ | * Blobs | 11.68 KiB | *** |
+ | * Tags | 132 B | |
+ | * Disk size | $(object_type_disk_usage all true) | |
+ | * Commits | $(object_type_disk_usage commit true) | *** |
+ | * Trees | $(object_type_disk_usage tree true) | ******** |
+ | * Blobs | $(object_type_disk_usage blob true) | ****** |
+ | * Tags | $(object_type_disk_usage tag) B | ***** |
+ | | | |
+ | * Largest objects | | |
+ | * Commits | | |
+ | * Maximum size [1] | 223 B | *** |
+ | * Maximum parents [2] | 1 | |
+ | * Trees | | |
+ | * Maximum size [3] | 32.29 KiB | |
+ | * Maximum entries [4] | 1.01 k | |
+ | * Blobs | | |
+ | * Maximum size [5] | 13 B | * |
+ | * Tags | | |
+ | * Maximum size [6] | 132 B | |
+
+ [1] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a
+ [2] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a
+ [3] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c
+ [4] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c
+ [5] 97d808e45116bf02103490294d3d46dad7a2ac62
+ [6] 4dae4f5954f5e6feb3577cfb1b181daa3fd3afd2
+ EOF
+
+ git repo structure --concern >actual 2>err &&
+
+ test_cmp expect actual &&
+ test_line_count = 0 err
+ )
+'
+
test_expect_success SHA1 'lines and nul format' '
test_when_finished "rm -rf repo" &&
git init repo &&
@@ -139,16 +292,18 @@ test_expect_success SHA1 'lines format with concerns' '
git init repo &&
(
cd repo &&
+ # differences between zlib and zlib-ng can cause this
+ # test to fail; so we just turn it off.
+ git config core.compression 0 &&
# configure thresholds to get concern indications
- git config repo.structure.concern.commit-count 7 &&
- git config repo.structure.concern.commit-size 14 &&
- git config repo.structure.concern.tree-size 10 &&
- git config repo.structure.concern.blob-size 8 &&
- git config repo.structure.concern.blob-inflated-size 10 &&
- git config repo.structure.concern.tag-size 2 &&
- git config repo.structure.concern.commit-largest 4 &&
- git config repo.structure.concern.blob-largest 2 &&
- touch configured &&
+ git config repo.concern.commit-count 7 &&
+ git config repo.concern.commit-size 14 &&
+ git config repo.concern.tree-size 10 &&
+ 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 &&
test_commit_bulk 1005 &&
git tag -a foo -m bar &&
oid="$(git rev-parse HEAD)" &&
@@ -157,7 +312,6 @@ test_expect_success SHA1 'lines format with concerns' '
# Also creates a commit, tree, and blob.
git notes add -m foo &&
-
# The tags disk size is handled specially due to the
# git-rev-list(1) --disk-usage=human option printing the full
# "byte/bytes" unit string instead of just "B".
@@ -172,8 +326,8 @@ test_expect_success SHA1 'lines format with concerns' '
objects.tags.inflated_size.concern_level=0
objects.commits.disk_size.concern_level=3
objects.trees.disk_size.concern_level=8
- objects.blobs.blob_size.concern_level=6
- objects.tags.disk_size.concern_level=4
+ objects.blobs.disk_size.concern_level=6
+ objects.tags.disk_size.concern_level=5
objects.commits.max_size.concern_level=3
objects.trees.max_size.concern_level=0
objects.blobs.max_size.concern_level=1
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [PATCH v2 3/3] repo: add documentation for level-of-concern.
2026-10-27 14:55 ` Patrick Steinhardt
@ 2026-10-28 13:23 ` Mark C. Chu-Carroll
0 siblings, 0 replies; 15+ messages in thread
From: Mark C. Chu-Carroll @ 2026-10-28 13:23 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: git
On Tue, 27 Oct 2026 15:55:31 +0100, Patrick Steinhardt <ps@pks.im> wrote:
> On Mon, Oct 26, 2026 at 01:43:56PM -0400, Mark C. Chu-Carroll wrote:
> > ---
> > Documentation/config/repo.adoc | 59 ++++++++++++++++++++++++++++++++++++++++++
> > Documentation/git-repo.adoc | 27 +++++++++++++++++--
> > 2 files changed, 84 insertions(+), 2 deletions(-)
> >
> > diff --git a/Documentation/config/repo.adoc b/Documentation/config/repo.adoc
> > new file mode 100644
> > index 0000000000..811b099d72
> > --- /dev/null
> > +++ b/Documentation/config/repo.adoc
> > @@ -0,0 +1,59 @@
> > +`repo.concern.commit-count`::
> > + Sets the value of the concern threshold for commit count.
> > +
> > +`repo.concern.tree-count`::
> > + Sets the value of the concern threshold for tree count.
>
> I think this commit can be split and squashed into the other two
> commits:
>
> - The change to synopsis and documentation of "--concern" should be
> squashed into the first commit.
>
> - These config values here should be squashed into the second commit.
Yeah, that makes sense.
(Note, this will appear out of order; original send failed yesterday.)
-Mark
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-10-28 13:23 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-21 13:55 [PATCH 0/3] repo: add degree-of-concern reporting to structure Mark C. Chu-Carroll
2026-10-21 13:55 ` [PATCH 1/3] repo: add level of concern reporting Mark C. Chu-Carroll
2026-10-21 13:55 ` [PATCH 2/3] repo: make concern thresholds configurable Mark C. Chu-Carroll
2026-10-21 13:55 ` [PATCH 3/3] repo: add documentation for level-of-concern Mark C. Chu-Carroll
2026-10-26 17:43 ` [PATCH v2 0/3] repo: add degree-of-concern reporting to structure Mark C. Chu-Carroll
2026-10-26 17:43 ` [PATCH v2 1/3] repo: add level of concern reporting Mark C. Chu-Carroll
2026-10-27 14:55 ` Patrick Steinhardt
2026-10-26 17:43 ` [PATCH v2 2/3] repo: make concern thresholds configurable Mark C. Chu-Carroll
2026-10-27 14:55 ` Patrick Steinhardt
2026-10-26 17:43 ` [PATCH v2 3/3] repo: add documentation for level-of-concern Mark C. Chu-Carroll
2026-10-27 14:55 ` Patrick Steinhardt
2026-10-28 13:23 ` Mark C. Chu-Carroll
2026-10-27 15:39 ` [PATCH v3 0/2] repo: add degree-of-concern reporting to structure Mark C. Chu-Carroll
2026-10-27 15:39 ` [PATCH v3 1/2] repo: add level of concern reporting Mark C. Chu-Carroll
2026-10-27 15:39 ` [PATCH v3 2/2] repo: make concern thresholds configurable Mark C. Chu-Carroll
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox