* [PATCH 0/1] repo: add filtering options to "repo structure"
@ 2026-09-24 16:45 Mark C. Chu-Carroll
2026-09-24 16:45 ` [PATCH 1/1] " Mark C. Chu-Carroll
0 siblings, 1 reply; 12+ messages in thread
From: Mark C. Chu-Carroll @ 2026-09-24 16:45 UTC (permalink / raw)
To: git; +Cc: jltobler, Mark C. Chu-Carroll
repo: add filtering options to "repo structure"
"git repo structure" provides a collection of useful information
about the information stored in a repo. In particular, it's
valuable for diagnosing performance issues caused by large objects
stored in a repo.
The current implementation of "git repo stucture" provides summary
information about everything in the repository - all of the
branches, remotes, tags, stashes, and notes. But sometimes
to properly diagnose a problem, it's useful to be able to exclude
refs that are known to not be relevant to the issue at hand.
Add a set of flags that allow a user to selective exclude
reference types from the report generated by "git repo structure".
When a ref type is excluded by the filter, it no longer appears
in the report (ie, if "--no-tags" is passed, the report line
for "Branches" will no longer appear under "* References").
Following the pattern of flags that are only used to
disable functionality (eg, "--no-verify" in "builtins/push.c"),
only the "--no-<reftype>" syntax is listed in the updated
documentation.
Overview of the changes:
- Add an enum to represent the structure flags.
- Add structure flags to the options for the "repo structure" commands.
- For each reference flag, add a conditional in "count_references"
which decides whether or not to add a ref to the pending list.
If an references is not added to the pending list, the things it
transitively references will not be added to the stats.
- Add a set of test cases to verify that reference counts
in the repo structure report correctly omit the specified
resource types.
- Update the documentation for git-repo to include the new options.
Mark C. Chu-Carroll (1):
repo: add filtering options to "repo structure"
Documentation/git-repo.adoc | 35 ++++-
builtin/repo.c | 158 +++++++++++++++------
t/t1901-repo-structure.sh | 276 ++++++++++++++++++++++++++++++++++++
3 files changed, 427 insertions(+), 42 deletions(-)
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 1/1] repo: add filtering options to "repo structure"
2026-09-24 16:45 [PATCH 0/1] repo: add filtering options to "repo structure" Mark C. Chu-Carroll
@ 2026-09-24 16:45 ` Mark C. Chu-Carroll
2026-09-30 16:28 ` Patrick Steinhardt
` (2 more replies)
0 siblings, 3 replies; 12+ messages in thread
From: Mark C. Chu-Carroll @ 2026-09-24 16:45 UTC (permalink / raw)
To: git; +Cc: jltobler, Mark C. Chu-Carroll
"git repo structure" provides a collection of useful information
about the information stored in a repo. In particular, it's
valuable for diagnosing performance issues caused by large objects
stored in a repo.
The current implementation of "git repo stucture" provides summary
information about everything in the repository - all of the
branches, remotes, tags, stashes, and notes. But sometimes
to properly diagnose a problem, it's useful to be able to exclude
refs that are known to not be relevant to the issue at hand.
Add a set of flags that allow a user to selective exclude
reference types from the report generated by "git repo structure".
When a ref type is excluded by the filter, it no longer appears
in the report (ie, if "--no-tags" is passed, the report line
for "Branches" will no longer appear under "* References").
Following the pattern of flags that are only used to
disable functionality (eg, "--no-verify" in "builtins/push.c"),
only the "--no-<reftype>" syntax is listed in the updated
documentation.
Overview of the changes:
- Add an enum to represent the structure flags.
- Add structure flags to the options for the "repo structure" commands.
- For each reference flag, add a conditional in "count_references"
which decides whether or not to add a ref to the pending list.
If an references is not added to the pending list, the things it
transitively references will not be added to the stats.
- Add a set of test cases to verify that reference counts
in the repo structure report correctly omit the specified
resource types.
- Update the documentation for git-repo to include the new options.
Signed-off-by: Mark C. Chu-Carroll <markchucarroll@fastmail.com>
---
Documentation/git-repo.adoc | 35 ++++-
builtin/repo.c | 158 +++++++++++++++------
t/t1901-repo-structure.sh | 276 ++++++++++++++++++++++++++++++++++++
3 files changed, 427 insertions(+), 42 deletions(-)
diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc
index ed7d80c690..463260cd23 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-branches] [--no-tags] [--no-remotes] [--no-notes] [--no-stashes]`::
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-branches] [--no-tags] [--no-remotes] [--no-notes] [--no-stashes]`::
Retrieve statistics about the current repository structure. The
following kinds of information are reported:
+
@@ -66,6 +66,23 @@ supported:
* Total disk size of reachable objects by type
* Largest reachable objects in the repository by type
+
+The types of objects counted can be filtered using flags:
++
+`--no-branches`:::
+ Omit branch refs from the set of objects to count.
+
+`--no-tags`:::
+ Omit tags from the set of objects to count.
+
+`--no-remotes`:::
+ Omit remotes from the set of objects to count.
+
+`--no-notes`:::
+ Omit notes from the set of objects to count.
+
+`--no-stashes`:::
+ Omit stashes from the set of objects to count.
++
The output format can be chosen through the flag `--format`. Three formats are
supported:
+
@@ -141,6 +158,20 @@ using the `nul` format:
git repo info --format=nul layout.bare layout.shallow
------------
+* Generates information about storage usage in the repository:
++
+------------
+git repo structure
+------------
++
+
+* Generates information about storage usage in the repository omitting
+remotes:
++
+------------
+git repo structure --no-remotes
+------------
+
SEE ALSO
--------
linkgit:git-rev-parse[1]
diff --git a/builtin/repo.c b/builtin/repo.c
index 84e012f83f..c8f6e38011 100644
--- a/builtin/repo.c
+++ b/builtin/repo.c
@@ -53,6 +53,20 @@ enum output_format {
FORMAT_NUL_TERMINATED,
};
+enum repo_structure_filter_flags {
+ REPO_STRUCTURE_FILTER_BRANCHES = (1 << 0),
+ REPO_STRUCTURE_FILTER_TAGS = (1 << 1),
+ REPO_STRUCTURE_FILTER_REMOTES = (1 << 2),
+ REPO_STRUCTURE_FILTER_NOTES = (1 << 3),
+ REPO_STRUCTURE_FILTER_STASHES = (1 << 4),
+};
+
+#define REPO_STRUCTURE_FILTER_FLAGS_ALL (REPO_STRUCTURE_FILTER_BRANCHES | \
+ REPO_STRUCTURE_FILTER_TAGS | \
+ REPO_STRUCTURE_FILTER_REMOTES | \
+ REPO_STRUCTURE_FILTER_NOTES |\
+ REPO_STRUCTURE_FILTER_STASHES)
+
struct repo_info_field {
const char *key;
get_value_fn *get_value;
@@ -490,7 +504,8 @@ static inline size_t get_total_object_values(struct object_values *values)
}
static void stats_table_setup_structure(struct stats_table *table,
- struct repo_structure *stats)
+ struct repo_structure *stats,
+ enum repo_structure_filter_flags flags)
{
struct object_stats *objects = &stats->objects;
struct ref_stats *refs = &stats->refs;
@@ -502,9 +517,15 @@ 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"));
+ if (flags & REPO_STRUCTURE_FILTER_BRANCHES) {
+ stats_table_count_addf(table, refs->branches, " * %s", _("Branches"));
+ }
+ if (flags & REPO_STRUCTURE_FILTER_TAGS) {
+ stats_table_count_addf(table, refs->tags, " * %s", _("Tags"));
+ }
+ if (flags & REPO_STRUCTURE_FILTER_REMOTES) {
+ stats_table_count_addf(table, refs->remotes, " * %s", _("Remotes"));
+ }
stats_table_count_addf(table, refs->others, " * %s", _("Others"));
object_count_total = get_total_object_values(&objects->type_counts);
@@ -517,8 +538,10 @@ static void stats_table_setup_structure(struct stats_table *table,
" * %s", _("Trees"));
stats_table_count_addf(table, objects->type_counts.blobs,
" * %s", _("Blobs"));
- stats_table_count_addf(table, objects->type_counts.tags,
- " * %s", _("Tags"));
+ if (flags & REPO_STRUCTURE_FILTER_TAGS) {
+ stats_table_count_addf(table, objects->type_counts.tags,
+ " * %s", _("Tags"));
+ }
inflated_object_total = get_total_object_values(&objects->inflated_sizes);
stats_table_size_addf(table, inflated_object_total,
@@ -529,8 +552,11 @@ static void stats_table_setup_structure(struct stats_table *table,
" * %s", _("Trees"));
stats_table_size_addf(table, objects->inflated_sizes.blobs,
" * %s", _("Blobs"));
- stats_table_size_addf(table, objects->inflated_sizes.tags,
- " * %s", _("Tags"));
+
+ if (flags & REPO_STRUCTURE_FILTER_TAGS) {
+ stats_table_size_addf(table, objects->inflated_sizes.tags,
+ " * %s", _("Tags"));
+ }
disk_object_total = get_total_object_values(&objects->disk_sizes);
stats_table_size_addf(table, disk_object_total,
@@ -541,8 +567,10 @@ static void stats_table_setup_structure(struct stats_table *table,
" * %s", _("Trees"));
stats_table_size_addf(table, objects->disk_sizes.blobs,
" * %s", _("Blobs"));
- stats_table_size_addf(table, objects->disk_sizes.tags,
- " * %s", _("Tags"));
+ if (flags & REPO_STRUCTURE_FILTER_TAGS) {
+ stats_table_size_addf(table, objects->disk_sizes.tags,
+ " * %s", _("Tags"));
+ }
stats_table_addf(table, "");
stats_table_addf(table, "* %s", _("Largest objects"));
@@ -569,11 +597,13 @@ static void stats_table_setup_structure(struct stats_table *table,
&objects->largest.blob_size.oid,
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,
- " * %s", _("Maximum size"));
+ if (flags & REPO_STRUCTURE_FILTER_TAGS) {
+ stats_table_addf(table, " * %s", _("Tags"));
+ stats_table_object_size_addf(table,
+ &objects->largest.tag_size.oid,
+ objects->largest.tag_size.value,
+ " * %s", _("Maximum size"));
+ }
}
#define INDEX_WIDTH 4
@@ -681,14 +711,21 @@ 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,
+ enum repo_structure_filter_flags flags)
{
- print_keyvalue("references.branches.count", key_delim,
- stats->refs.branches, value_delim);
+ if (flags & REPO_STRUCTURE_FILTER_BRANCHES) {
+ print_keyvalue("references.branches.count", key_delim,
+ stats->refs.branches, value_delim);
+ }
+ if (flags & REPO_STRUCTURE_FILTER_TAGS) {
print_keyvalue("references.tags.count", key_delim,
stats->refs.tags, value_delim);
- print_keyvalue("references.remotes.count", key_delim,
- stats->refs.remotes, value_delim);
+ }
+ if (flags & REPO_STRUCTURE_FILTER_REMOTES) {
+ print_keyvalue("references.remotes.count", key_delim,
+ stats->refs.remotes, value_delim);
+ }
print_keyvalue("references.others.count", key_delim,
stats->refs.others, value_delim);
@@ -698,8 +735,10 @@ static void structure_keyvalue_print(struct repo_structure *stats,
stats->objects.type_counts.trees, value_delim);
print_keyvalue("objects.blobs.count", key_delim,
stats->objects.type_counts.blobs, value_delim);
- print_keyvalue("objects.tags.count", key_delim,
- stats->objects.type_counts.tags, value_delim);
+ if (flags & REPO_STRUCTURE_FILTER_TAGS) {
+ print_keyvalue("objects.tags.count", key_delim,
+ stats->objects.type_counts.tags, value_delim);
+ }
print_keyvalue("objects.commits.inflated_size", key_delim,
stats->objects.inflated_sizes.commits, value_delim);
@@ -707,8 +746,10 @@ static void structure_keyvalue_print(struct repo_structure *stats,
stats->objects.inflated_sizes.trees, value_delim);
print_keyvalue("objects.blobs.inflated_size", key_delim,
stats->objects.inflated_sizes.blobs, value_delim);
- print_keyvalue("objects.tags.inflated_size", key_delim,
- stats->objects.inflated_sizes.tags, value_delim);
+ if (flags & REPO_STRUCTURE_FILTER_TAGS) {
+ print_keyvalue("objects.tags.inflated_size", key_delim,
+ stats->objects.inflated_sizes.tags, value_delim);
+ }
print_keyvalue("objects.commits.disk_size", key_delim,
stats->objects.disk_sizes.commits, value_delim);
@@ -716,8 +757,10 @@ static void structure_keyvalue_print(struct repo_structure *stats,
stats->objects.disk_sizes.trees, value_delim);
print_keyvalue("objects.blobs.disk_size", key_delim,
stats->objects.disk_sizes.blobs, value_delim);
- print_keyvalue("objects.tags.disk_size", key_delim,
- stats->objects.disk_sizes.tags, value_delim);
+ if (flags & REPO_STRUCTURE_FILTER_TAGS) {
+ print_keyvalue("objects.tags.disk_size", key_delim,
+ stats->objects.disk_sizes.tags, value_delim);
+ }
print_object_data("objects.commits.max_size", key_delim,
&stats->objects.largest.commit_size, value_delim);
@@ -725,8 +768,10 @@ static void structure_keyvalue_print(struct repo_structure *stats,
&stats->objects.largest.tree_size, value_delim);
print_object_data("objects.blobs.max_size", key_delim,
&stats->objects.largest.blob_size, value_delim);
- print_object_data("objects.tags.max_size", key_delim,
- &stats->objects.largest.tag_size, value_delim);
+ if (flags & REPO_STRUCTURE_FILTER_TAGS) {
+ print_object_data("objects.tags.max_size", key_delim,
+ &stats->objects.largest.tag_size, value_delim);
+ }
print_object_data("objects.commits.max_parents", key_delim,
&stats->objects.largest.parent_count, value_delim);
@@ -739,6 +784,7 @@ static void structure_keyvalue_print(struct repo_structure *stats,
struct count_references_data {
struct ref_stats *stats;
struct rev_info *revs;
+ enum repo_structure_filter_flags flags;
struct progress *progress;
};
@@ -747,19 +793,42 @@ static int count_references(const struct reference *ref, void *cb_data)
struct count_references_data *data = cb_data;
struct ref_stats *stats = data->stats;
size_t ref_count;
+ bool add_pending = false;
switch (ref_kind_from_refname(ref->name)) {
case FILTER_REFS_BRANCHES:
- stats->branches++;
+ if (data->flags & REPO_STRUCTURE_FILTER_BRANCHES) {
+ stats->branches++;
+ add_pending = true;
+ }
break;
case FILTER_REFS_REMOTES:
- stats->remotes++;
+ if (data->flags & REPO_STRUCTURE_FILTER_REMOTES) {
+ stats->remotes++;
+ add_pending = true;
+ }
break;
case FILTER_REFS_TAGS:
- stats->tags++;
+ if (data->flags & REPO_STRUCTURE_FILTER_TAGS) {
+ stats->tags++;
+ add_pending = true;
+ }
break;
case FILTER_REFS_OTHERS:
- stats->others++;
+ if (!strcmp(ref->name, "refs/stash")) {
+ if (data->flags & REPO_STRUCTURE_FILTER_STASHES) {
+ stats->others++;
+ add_pending = true;
+ }
+ } else if (starts_with(ref->name, "refs/notes")) {
+ if (data->flags & REPO_STRUCTURE_FILTER_NOTES) {
+ stats->others++;
+ add_pending = true;
+ }
+ } else {
+ stats->others++;
+ add_pending = true;
+ }
break;
default:
BUG("unexpected reference type");
@@ -769,10 +838,11 @@ static int count_references(const struct reference *ref, void *cb_data)
* While iterating through references for counting, also add OIDs in
* preparation for the path walk.
*/
- add_pending_oid(data->revs, NULL, ref->oid, 0);
-
- ref_count = get_total_reference_count(stats);
- display_progress(data->progress, ref_count);
+ if (add_pending) {
+ add_pending_oid(data->revs, NULL, ref->oid, 0);
+ ref_count = get_total_reference_count(stats);
+ display_progress(data->progress, ref_count);
+ }
return 0;
}
@@ -780,11 +850,13 @@ static int count_references(const struct reference *ref, void *cb_data)
static void structure_count_references(struct ref_stats *stats,
struct rev_info *revs,
struct repository *repo,
+ enum repo_structure_filter_flags flags,
int show_progress)
{
struct count_references_data data = {
.stats = stats,
.revs = revs,
+ .flags = flags,
};
if (show_progress)
@@ -935,6 +1007,7 @@ 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 flags = REPO_STRUCTURE_FILTER_FLAGS_ALL;
struct option options[] = {
OPT_CALLBACK_F(0, "format", &format, N_("format"),
N_("output format"),
@@ -944,6 +1017,11 @@ 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_BIT(0, "branches", &flags, N_("include branches in structure statistics"), REPO_STRUCTURE_FILTER_BRANCHES),
+ OPT_BIT(0, "tags", &flags, N_("include tags in structure statistics"), REPO_STRUCTURE_FILTER_TAGS),
+ OPT_BIT(0, "remotes", &flags, N_("include remotes in structure statistics"), REPO_STRUCTURE_FILTER_REMOTES),
+ OPT_BIT(0, "notes", &flags, N_("include notes in structure statistics"), REPO_STRUCTURE_FILTER_NOTES),
+ OPT_BIT(0, "stashes", &flags, N_("include stashes in structure statistics"), REPO_STRUCTURE_FILTER_STASHES),
OPT_END()
};
@@ -956,19 +1034,19 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
if (show_progress < 0)
show_progress = isatty(2);
- structure_count_references(&stats.refs, &revs, repo, show_progress);
+ structure_count_references(&stats.refs, &revs, repo, flags, show_progress);
structure_count_objects(&stats.objects, &revs, repo, show_progress);
switch (format) {
case FORMAT_TABLE:
- stats_table_setup_structure(&table, &stats);
+ stats_table_setup_structure(&table, &stats, flags);
stats_table_print_structure(&table);
break;
case FORMAT_NEWLINE_TERMINATED:
- structure_keyvalue_print(&stats, '=', '\n');
+ structure_keyvalue_print(&stats, '=', '\n', flags);
break;
case FORMAT_NUL_TERMINATED:
- structure_keyvalue_print(&stats, '\n', '\0');
+ structure_keyvalue_print(&stats, '\n', '\0', flags);
break;
default:
BUG("invalid output format");
diff --git a/t/t1901-repo-structure.sh b/t/t1901-repo-structure.sh
index 02cc2b594a..60a82493c6 100755
--- a/t/t1901-repo-structure.sh
+++ b/t/t1901-repo-structure.sh
@@ -144,6 +144,170 @@ test_expect_success SHA1 'repository with references and objects' '
)
'
+
+test_expect_success SHA1 'repository with references and objects, filtered with --no-origins' '
+ 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" &&
+
+ git checkout -b other_branch &&
+ test_commit_bulk 5 &&
+
+ git checkout master &&
+
+ # 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 | 2 |
+ | * Tags | 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.04 MiB |
+ | * Commits | 219.00 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] 0a8f6a47f34078bb7c4b2bb4377c2133157f6703
+ [3] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c
+ [4] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c
+ [5] 97d808e45116bf02103490294d3d46dad7a2ac62
+ [6] 4dae4f5954f5e6feb3577cfb1b181daa3fd3afd2
+ EOF
+ git repo structure --no-remotes >actual 2>err &&
+ test_cmp expect actual &&
+ test_line_count = 0 err
+ )
+'
+
+test_expect_success SHA1 'repository with references and objects, filtered with "--no-branches"' '
+ 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" &&
+
+ git checkout -b other_branch &&
+ test_commit_bulk 5 &&
+
+ git checkout master &&
+
+ # Also creates a commit, tree, and blob.
+ git notes add -m foo &&
+
+ git repo structure --no-branches >actual 2>err &&
+ test_line_count = 0 err &&
+ test_grep "| \* Count | 3 |" actual &&
+ test_grep "| \* Others | 1 |" actual &&
+ test_grep ! "Branches" actual
+ )
+'
+
+test_expect_success SHA1 'repository with references and objects, filtered with "--no-notes"' '
+ 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" &&
+
+ git checkout -b other_branch &&
+ test_commit_bulk 5 &&
+
+ git checkout master &&
+
+ # Also creates a commit, tree, and blob.
+ git notes add -m foo &&
+
+ git repo structure --no-notes >actual 2>err &&
+ test_line_count = 0 err &&
+ test_grep "| \* Count | 4 |" actual &&
+ test_grep "| \* Others | 0 |" actual &&
+ test_grep "| \* Commits | 218.81 KiB |" actual
+ )
+'
+
+test_expect_success SHA1 'repository with references and objects, filtered with multiple flags' '
+ 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" &&
+
+ git checkout -b other_branch &&
+ test_commit_bulk 5 &&
+
+ git checkout master &&
+
+ # Also creates a commit, tree, and blob.
+ git notes add -m foo &&
+
+ git repo structure --no-notes --no-branches >actual 2>err &&
+ test_line_count = 0 err &&
+ test_grep "| \* Count | 2 |" actual &&
+ test_grep "| \* Others | 0 |" actual &&
+ test_grep "| \* Commits | 217.73 KiB |" actual &&
+ test_grep ! "Branches" actual &&
+
+ git repo structure --no-notes --no-branches --no-remotes >actual 2>err &&
+ test_line_count = 0 err &&
+ test_grep "| \* Count | 1 |" actual &&
+ test_grep "| \* Others | 0 |" actual &&
+ test_grep "| \* Commits | 217.73 KiB |" actual &&
+ test_grep ! "Branches" actual &&
+ test_grep ! "Remotes" actual
+ )
+'
+
test_expect_success SHA1 'lines and nul format' '
test_when_finished "rm -rf repo" &&
git init repo &&
@@ -203,6 +367,118 @@ test_expect_success SHA1 'lines and nul format' '
test_line_count = 0 err
)
'
+test_expect_success SHA1 'lines and nul format with --no-tags filter' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit_bulk 42 &&
+ git tag -a foo -m bar &&
+
+ cat >expect <<-EOF &&
+ references.branches.count=1
+ references.remotes.count=0
+ references.others.count=0
+ objects.commits.count=42
+ objects.trees.count=42
+ objects.blobs.count=42
+ objects.commits.inflated_size=9225
+ objects.trees.inflated_size=28554
+ objects.blobs.inflated_size=453
+ objects.commits.disk_size=$(object_type_disk_usage commit)
+ objects.trees.disk_size=$(object_type_disk_usage tree)
+ objects.blobs.disk_size=$(object_type_disk_usage blob)
+ objects.commits.max_size=221
+ objects.commits.max_size_oid=de3508174b5c2ace6993da67cae9be9069e2df39
+ objects.trees.max_size=1335
+ objects.trees.max_size_oid=09931deea9d81ec21300d3e13c74412f32eacec5
+ objects.blobs.max_size=11
+ objects.blobs.max_size_oid=eaeeedced46482bd4281fda5a5f05ce24854151f
+ objects.commits.max_parents=1
+ objects.commits.max_parents_oid=de3508174b5c2ace6993da67cae9be9069e2df39
+ objects.trees.max_entries=42
+ objects.trees.max_entries_oid=09931deea9d81ec21300d3e13c74412f32eacec5
+ EOF
+
+ git repo structure --format=lines --no-tags >actual 2>err &&
+ test_cmp expect actual &&
+ test_line_count = 0 err &&
+
+ git repo structure --format=nul --no-tags >out 2>err &&
+ tr "\012\000" "=\012" <out >actual &&
+
+ test_cmp expect actual &&
+ test_line_count = 0 err &&
+
+ # "-z", as a synonym to "--format=nul", participates in the
+ # usual "last one wins" rule.
+ git repo structure --format=table -z --no-tags >out 2>err &&
+ tr "\012\000" "=\012" <out >actual &&
+
+ test_cmp expect actual &&
+ test_line_count = 0 err
+ )
+'
+
+test_expect_success SHA1 'lines and nul format with --no-branches filter' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit_bulk 42 &&
+ git tag -a foo -m bar &&
+
+ cat >expect <<-EOF &&
+ references.tags.count=1
+ references.remotes.count=0
+ references.others.count=0
+ objects.commits.count=42
+ objects.trees.count=42
+ objects.blobs.count=42
+ objects.tags.count=1
+ objects.commits.inflated_size=9225
+ objects.trees.inflated_size=28554
+ objects.blobs.inflated_size=453
+ objects.tags.inflated_size=132
+ objects.commits.disk_size=$(object_type_disk_usage commit)
+ objects.trees.disk_size=$(object_type_disk_usage tree)
+ objects.blobs.disk_size=$(object_type_disk_usage blob)
+ objects.tags.disk_size=$(object_type_disk_usage tag)
+ objects.commits.max_size=221
+ objects.commits.max_size_oid=de3508174b5c2ace6993da67cae9be9069e2df39
+ objects.trees.max_size=1335
+ objects.trees.max_size_oid=09931deea9d81ec21300d3e13c74412f32eacec5
+ objects.blobs.max_size=11
+ objects.blobs.max_size_oid=eaeeedced46482bd4281fda5a5f05ce24854151f
+ objects.tags.max_size=132
+ objects.tags.max_size_oid=1ee0f2b16ea37d895dbe9dbd76cd2ac70446176c
+ objects.commits.max_parents=1
+ objects.commits.max_parents_oid=de3508174b5c2ace6993da67cae9be9069e2df39
+ objects.trees.max_entries=42
+ objects.trees.max_entries_oid=09931deea9d81ec21300d3e13c74412f32eacec5
+ EOF
+
+ git repo structure --format=lines --no-branches >actual 2>err &&
+ cp expect /tmp/expect &&
+ cp actual /tmp/actual &&
+ test_cmp expect actual &&
+ test_line_count = 0 err &&
+
+ git repo structure --format=nul --no-branches >out 2>err &&
+ tr "\012\000" "=\012" <out >actual &&
+
+ test_cmp expect actual &&
+ test_line_count = 0 err &&
+
+ # "-z", as a synonym to "--format=nul", participates in the
+ # usual "last one wins" rule.
+ git repo structure --format=table -z --no-branches >out 2>err &&
+ tr "\012\000" "=\012" <out >actual &&
+
+ test_cmp expect actual &&
+ test_line_count = 0 err
+ )
+'
test_expect_success 'progress meter option' '
test_when_finished "rm -rf repo" &&
--
2.53.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH 1/1] repo: add filtering options to "repo structure"
2026-09-24 16:45 ` [PATCH 1/1] " Mark C. Chu-Carroll
@ 2026-09-30 16:28 ` Patrick Steinhardt
2026-10-05 18:52 ` Mark C. Chu-Carroll
2026-10-05 17:40 ` [PATCH v2 0/1] " Mark C. Chu-Carroll
2026-10-09 18:09 ` [PATCH v3 0/1] repo: add revision " Mark C. Chu-Carroll
2 siblings, 1 reply; 12+ messages in thread
From: Patrick Steinhardt @ 2026-09-30 16:28 UTC (permalink / raw)
To: Mark C. Chu-Carroll; +Cc: git, jltobler
On Thu, Sep 24, 2026 at 12:45:03PM -0400, Mark C. Chu-Carroll wrote:
> "git repo structure" provides a collection of useful information
> about the information stored in a repo. In particular, it's
> valuable for diagnosing performance issues caused by large objects
> stored in a repo.
>
> The current implementation of "git repo stucture" provides summary
> information about everything in the repository - all of the
> branches, remotes, tags, stashes, and notes. But sometimes
> to properly diagnose a problem, it's useful to be able to exclude
> refs that are known to not be relevant to the issue at hand.
Yes, indeed. Sometimes you may for example want to figure out where
exactly the storage size of a particular repository is going. Or in the
case of GitLab for example, we may have bookkeeping references that are
not controllable by customers. So we may only want to get the structure
for all the customer-controllable branches there.
> Add a set of flags that allow a user to selective exclude
> reference types from the report generated by "git repo structure".
> When a ref type is excluded by the filter, it no longer appears
> in the report (ie, if "--no-tags" is passed, the report line
> for "Branches" will no longer appear under "* References").
> Following the pattern of flags that are only used to
> disable functionality (eg, "--no-verify" in "builtins/push.c"),
> only the "--no-<reftype>" syntax is listed in the updated
> documentation.
Hmm, okay. I would have expected that the user can essentially pass
arbitrary revisions as understood by git-log(1) et al. And if they pass
any such revisions, we should not enumerate anything but what they have
passed, so the flags shouldn't only be used to exclude.
So, for example:
$ git repo structure --branches
$ git repo structure master
$ git repo structure --all --not --branches
I would hope that git-repo(1) can achieve that rather easily because I
expect that it uses `struct rev_info`, but let's read on.
> Overview of the changes:
> - Add an enum to represent the structure flags.
> - Add structure flags to the options for the "repo structure" commands.
> - For each reference flag, add a conditional in "count_references"
> which decides whether or not to add a ref to the pending list.
> If an references is not added to the pending list, the things it
> transitively references will not be added to the stats.
> - Add a set of test cases to verify that reference counts
> in the repo structure report correctly omit the specified
> resource types.
> - Update the documentation for git-repo to include the new options.
Note that we typically don't have lists of what exactly has changed in
the commit. That kind of information is already visible from the diff
itself. So what the commit message itself should focus on is whether any
of these changes are non-obvious or whethere there's any dragons to be
found.
So in summary: everything that may surprise the reader should be part of
the commit message, everything that's just obvious plumbing doesn't
really have to be mentioned.
> diff --git a/builtin/repo.c b/builtin/repo.c
> index 84e012f83f..c8f6e38011 100644
> --- a/builtin/repo.c
> +++ b/builtin/repo.c
> @@ -490,7 +504,8 @@ static inline size_t get_total_object_values(struct object_values *values)
> }
>
> static void stats_table_setup_structure(struct stats_table *table,
> - struct repo_structure *stats)
> + struct repo_structure *stats,
> + enum repo_structure_filter_flags flags)
> {
> struct object_stats *objects = &stats->objects;
> struct ref_stats *refs = &stats->refs;
> @@ -502,9 +517,15 @@ 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"));
> + if (flags & REPO_STRUCTURE_FILTER_BRANCHES) {
> + stats_table_count_addf(table, refs->branches, " * %s", _("Branches"));
> + }
> + if (flags & REPO_STRUCTURE_FILTER_TAGS) {
> + stats_table_count_addf(table, refs->tags, " * %s", _("Tags"));
> + }
> + if (flags & REPO_STRUCTURE_FILTER_REMOTES) {
> + stats_table_count_addf(table, refs->remotes, " * %s", _("Remotes"));
> + }
> stats_table_count_addf(table, refs->others, " * %s", _("Others"));
>
> object_count_total = get_total_object_values(&objects->type_counts);
Coding style: we don't use curly braces around single-line statements.
But more importantly, I think this is where the mismatch in expectations
comes from that I was pointing out further up. My expectation was that
what we want to achieve is to filter the reachable objects by revisions,
which I think is a much more useful thing to do. But what the flags do
instead us to filter the output in the "References" count.
I think that we should rather go into the direction of filtering objects
and not the ref output, as the latter isn't all that useful. It _may_
make sense to maybe make some sections of the output optional, but
excluding individual ref types is arguably too fine-grained.
In any case, to go into the direction of filtering objects you'd want to
adapt `parse_options()` so that it accepts unknown options (you can
achieve that by passing `PARSE_OPT_KEEP_ARGV0 |
PARSE_OPT_KEEP_UNKNOWN_OPT`) and then pass argv to `setup_revisions()`.
And I think that _should_ already achieve proper filtering of objects by
revisions.
Thanks!
Patrick
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v2 0/1] repo: add filtering options to "repo structure"
2026-09-24 16:45 ` [PATCH 1/1] " Mark C. Chu-Carroll
2026-09-30 16:28 ` Patrick Steinhardt
@ 2026-10-05 17:40 ` Mark C. Chu-Carroll
2026-10-05 17:40 ` [PATCH v2 1/1] " Mark C. Chu-Carroll
2026-10-09 18:09 ` [PATCH v3 0/1] repo: add revision " Mark C. Chu-Carroll
2 siblings, 1 reply; 12+ messages in thread
From: Mark C. Chu-Carroll @ 2026-10-05 17:40 UTC (permalink / raw)
To: git; +Cc: jltobler, ps, Mark C. Chu-Carroll
"git repo structure" provides a collection of useful information
about the information stored in a repo. In particular, it's
valuable for diagnosing performance issues caused by large objects
stored in a repo.
The current implementation of "git repo stucture" provides summary
information about everything in the repository - all of the
branches, remotes, tags, stashes, and notes. But sometimes
to properly diagnose a problem, it's useful to be able to
get information about the specific part of the repo that's
exhibiting a problem.
Add the option to specify a set of filters in the form
of a list of include and exclude queries. Each of these
specifies a commit or ref or range. The set of objects processed
will consist of all objects reachable from any of the
includes which are _not_ reached exclusively by paths
including any of the excludes.
Updates since v1: this change has been completely rewritten. The
original version implemented filters the same way as
"git-sizer", by selecting object types to include or exclude.
This version drops he type-based filters in favor of
specifying object traversal roots and exclusions.
Mark C. Chu-Carroll (1):
repo: add filtering options to "repo structure"
Documentation/git-repo.adoc | 41 ++++++++++++++++--
builtin/repo.c | 16 +++++--
t/t1901-repo-structure.sh | 84 +++++++++++++++++++++++++++++++++++++
3 files changed, 133 insertions(+), 8 deletions(-)
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v2 1/1] repo: add filtering options to "repo structure"
2026-10-05 17:40 ` [PATCH v2 0/1] " Mark C. Chu-Carroll
@ 2026-10-05 17:40 ` Mark C. Chu-Carroll
2026-10-06 5:51 ` Patrick Steinhardt
0 siblings, 1 reply; 12+ messages in thread
From: Mark C. Chu-Carroll @ 2026-10-05 17:40 UTC (permalink / raw)
To: git; +Cc: jltobler, ps, Mark C. Chu-Carroll
Implement filtering for repo structure, imitating the mechanism
used in "git log".
Signed-off-by: Mark C. Chu-Carroll <markchucarroll@fastmail.com>
---
Documentation/git-repo.adoc | 41 ++++++++++++++++--
builtin/repo.c | 16 +++++--
t/t1901-repo-structure.sh | 84 +++++++++++++++++++++++++++++++++++++
3 files changed, 133 insertions(+), 8 deletions(-)
diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc
index ed7d80c690..5cbdf8e727 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] [<include|^exclude>...]
DESCRIPTION
-----------
@@ -56,9 +56,10 @@ supported:
`nul`:::
Similar to `lines`, but using a _NUL_ character after each value.
-`structure [--format=(table|lines|nul) | -z]`::
- Retrieve statistics about the current repository structure. The
- following kinds of information are reported:
+`structure [--format=(table|lines|nul) | -z] [<include|^exclude>...]::
++
+Retrieve statistics about the current repository structure. The
+following kinds of information are reported:
+
* Reference counts categorized by type
* Reachable object counts categorized by type
@@ -66,6 +67,16 @@ supported:
* Total disk size of reachable objects by type
* Largest reachable objects in the repository by type
+
+The set of objects counted can be filtered by specifying a
+collection of query clauses to select which objects will be
+counted. These parameters follow the same syntax as the parameters
+to similar commands like `git log`. Semantically, these parameters
+are treated as a collection of include and exclude specifiers. Th
+set of objects counted will consist of all objects reachable from
+an object included by one of the include specifiers via a path that
+does not include an object in an exclude clause. If no includes
+are specified, then the include set is all reachable objects.
++
The output format can be chosen through the flag `--format`. Three formats are
supported:
+
@@ -141,6 +152,28 @@ using the `nul` format:
git repo info --format=nul layout.bare layout.shallow
------------
+* Generates information about storage usage in the repository:
++
+------------
+git repo structure
+------------
++
+
+* Generates information about storage usage in the repository omitting
+the branch "foo":
++
+------------
+git repo structure ^foo
+------------
++
+* Generates information about repository objects reachable from
+the references "x" and "y", but omitting anything that can
+only be reached on a path including "xchild":
++
+------------
+git repo structure x y ^xchild
+------------
++
SEE ALSO
--------
linkgit:git-rev-parse[1]
diff --git a/builtin/repo.c b/builtin/repo.c
index 84e012f83f..b3aca71298 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] [<include|^exclude>...]"
static const char *const repo_usage[] = {
REPO_INFO_USAGE,
@@ -946,12 +946,20 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
OPT_END()
};
+ struct setup_revision_opt s_r_opt;
+ memset(&s_r_opt, 0, sizeof(s_r_opt));
+ s_r_opt.def = "HEAD";
+ s_r_opt.revarg_opt = REVARG_COMMITTISH;
- argc = parse_options(argc, argv, prefix, options, repo_structure_usage, 0);
- if (argc)
- usage(_("too many arguments"));
+ argc = parse_options(argc, argv, prefix, options, repo_structure_usage,
+ PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);
repo_init_revisions(repo, &revs, prefix);
+ if (argc > 1) {
+ argc = setup_revisions(argc, argv, &revs, &s_r_opt);
+ if (argc > 1)
+ usage(_("too many arguments"));
+ }
if (show_progress < 0)
show_progress = isatty(2);
diff --git a/t/t1901-repo-structure.sh b/t/t1901-repo-structure.sh
index 02cc2b594a..eb2c595955 100755
--- a/t/t1901-repo-structure.sh
+++ b/t/t1901-repo-structure.sh
@@ -144,6 +144,90 @@ test_expect_success SHA1 'repository with references and objects' '
)
'
+test_expect_success SHA1 'repository with references and objects, filtered' '
+ 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" &&
+ git checkout -b grobble &&
+ test_commit_bulk --ref=refs/heads/grobble 20 &&
+ git checkout master &&
+ test_commit_bulk 20 &&
+ # Also creates a commit, tree, and blob.
+ git notes add -m foo &&
+
+ # 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 | 5 |
+ | * Branches | 2 |
+ | * Tags | 1 |
+ | * Remotes | 1 |
+ | * Others | 1 |
+ | | |
+ | * Reachable objects | |
+ | * Count | 3.06 k |
+ | * Commits | 1.05 k |
+ | * Trees | 1.01 k |
+ | * Blobs | 1.01 k |
+ | * Tags | 1 |
+ | * Inflated size | 16.04 MiB |
+ | * Commits | 226.54 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] df6400c01440c329f1011669c4c26cc0c7852887
+ [3] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c
+ [4] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c
+ [5] 97d808e45116bf02103490294d3d46dad7a2ac62
+ [6] 4dae4f5954f5e6feb3577cfb1b181daa3fd3afd2
+ EOF
+
+ git repo structure >actual 2>actual-err &&
+ cp actual /tmp/actual &&
+ cp expect /tmp/expect &&
+ test_cmp expect actual &&
+ test_line_count = 0 actual-err &&
+
+ git repo structure grobble ^master >actual 2>actual-err &&
+ cp actual /tmp &&
+ cp actual-err /tmp &&
+ test_grep "| \* Commits | 21 |" actual &&
+ test_grep "| \* Trees | 2 |" actual &&
+ test_grep "| \* Commits | 4.50 KiB |" actual &&
+ test_grep "| \* Trees | 32.35 KiB |" actual &&
+ test_grep "| \* Blobs | 11.68 KiB |" actual &&
+ test_line_count = 0 actual-err
+ )
+'
+
test_expect_success SHA1 'lines and nul format' '
test_when_finished "rm -rf repo" &&
git init repo &&
--
2.53.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH 1/1] repo: add filtering options to "repo structure"
2026-09-30 16:28 ` Patrick Steinhardt
@ 2026-10-05 18:52 ` Mark C. Chu-Carroll
0 siblings, 0 replies; 12+ messages in thread
From: Mark C. Chu-Carroll @ 2026-10-05 18:52 UTC (permalink / raw)
To: Patrick Steinhardt, Mark C. Chu-Carroll; +Cc: git, jltobler
On Wed Sep 30, 2026 at 12:28 PM EDT, Patrick Steinhardt wrote:
> On Thu, Sep 24, 2026 at 12:45:03PM -0400, Mark C. Chu-Carroll wrote:
>> "git repo structure" provides a collection of useful information
>> about the information stored in a repo. In particular, it's
>> valuable for diagnosing performance issues caused by large objects
>> stored in a repo.
>>
>> The current implementation of "git repo stucture" provides summary
>> information about everything in the repository - all of the
>> branches, remotes, tags, stashes, and notes. But sometimes
>> to properly diagnose a problem, it's useful to be able to exclude
>> refs that are known to not be relevant to the issue at hand.
>
> Yes, indeed. Sometimes you may for example want to figure out where
> exactly the storage size of a particular repository is going. Or in the
> case of GitLab for example, we may have bookkeeping references that are
> not controllable by customers. So we may only want to get the structure
> for all the customer-controllable branches there.
>
>> Add a set of flags that allow a user to selective exclude
>> reference types from the report generated by "git repo structure".
>> When a ref type is excluded by the filter, it no longer appears
>> in the report (ie, if "--no-tags" is passed, the report line
>> for "Branches" will no longer appear under "* References").
>> Following the pattern of flags that are only used to
>> disable functionality (eg, "--no-verify" in "builtins/push.c"),
>> only the "--no-<reftype>" syntax is listed in the updated
>> documentation.
>
> Hmm, okay. I would have expected that the user can essentially pass
> arbitrary revisions as understood by git-log(1) et al. And if they pass
> any such revisions, we should not enumerate anything but what they have
> passed, so the flags shouldn't only be used to exclude.
>
> So, for example:
>
> $ git repo structure --branches
> $ git repo structure master
> $ git repo structure --all --not --branches
>
> I would hope that git-repo(1) can achieve that rather easily because I
> expect that it uses `struct rev_info`, but let's read on.
That makes sense. My initial understanding was that most of what
"git repo structure" does is internalize the functionality of git-sizer
into the core of git. The only filters offered by git-sizer are
type-based. But I agree that a commit list based filter is a lot
more useful, so I've updated the patch set to implement it.
-Mark
--
Mark Craig Chu-Carroll (@MarkChuCarroll at gitlab)
*** Software Tools/Math Geek - Software Engineer at Gitlab
*** Work Email: mcarroll@gitlab.com / markchucarroll@fastmail.com
*** Personal Blog: http://goodmath.org/blog / Personal email: markcc@gmail.com
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v2 1/1] repo: add filtering options to "repo structure"
2026-10-05 17:40 ` [PATCH v2 1/1] " Mark C. Chu-Carroll
@ 2026-10-06 5:51 ` Patrick Steinhardt
2026-10-08 15:46 ` Kaartic Sivaraam
0 siblings, 1 reply; 12+ messages in thread
From: Patrick Steinhardt @ 2026-10-06 5:51 UTC (permalink / raw)
To: Mark C. Chu-Carroll; +Cc: git, jltobler
On Mon, Oct 05, 2026 at 01:40:44PM -0400, Mark C. Chu-Carroll wrote:
> Implement filtering for repo structure, imitating the mechanism
> used in "git log".
The message should give an explanation of what this change does, and
what the motivation behind it is.
> diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc
> index ed7d80c690..5cbdf8e727 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] [<include|^exclude>...]
I think we should probably have this be `[<revs>...]`.
> @@ -56,9 +56,10 @@ supported:
> `nul`:::
> Similar to `lines`, but using a _NUL_ character after each value.
>
> -`structure [--format=(table|lines|nul) | -z]`::
> - Retrieve statistics about the current repository structure. The
> - following kinds of information are reported:
> +`structure [--format=(table|lines|nul) | -z] [<include|^exclude>...]::
Same here.
> @@ -66,6 +67,16 @@ supported:
> * Total disk size of reachable objects by type
> * Largest reachable objects in the repository by type
> +
> +The set of objects counted can be filtered by specifying a
> +collection of query clauses to select which objects will be
s/query clauses/revisions/, which is a well-defined term. So with this
change I think we can drop most of the remaining paragraph, except for
the last sentence.
> +counted. These parameters follow the same syntax as the parameters
> +to similar commands like `git log`. Semantically, these parameters
> +are treated as a collection of include and exclude specifiers. Th
> +set of objects counted will consist of all objects reachable from
> +an object included by one of the include specifiers via a path that
> +does not include an object in an exclude clause. If no includes
> +are specified, then the include set is all reachable objects.
> ++
> The output format can be chosen through the flag `--format`. Three formats are
> supported:
> +
> diff --git a/builtin/repo.c b/builtin/repo.c
> index 84e012f83f..b3aca71298 100644
> --- a/builtin/repo.c
> +++ b/builtin/repo.c
> @@ -946,12 +946,20 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
> OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
> OPT_END()
> };
> + struct setup_revision_opt s_r_opt;
> + memset(&s_r_opt, 0, sizeof(s_r_opt));
> + s_r_opt.def = "HEAD";
> + s_r_opt.revarg_opt = REVARG_COMMITTISH;
This can be:
struct setup_revision_opt s_r_opt = {
.def = "HEAD",
.revarg_opt = REVARG_COMMITTISH,
};
But I wonder whether we want to pass it at all:
- `.def` specifies the default, but do we even want to have one when
the user has passed arguments?
- `.revarg_opt` makes us treat it like a committish by default, but a
user may for example want to figure out the size of all objects
reachable from a specific tree, only.
So maybe we shouldn't be setting this at all and just pass `NULL` to
`setup_revisions()`?
> - argc = parse_options(argc, argv, prefix, options, repo_structure_usage, 0);
> - if (argc)
> - usage(_("too many arguments"));
> + argc = parse_options(argc, argv, prefix, options, repo_structure_usage,
> + PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);
Makes sense. Here we keep argv0 because of `setup_revisions()`' weird
calling convention. And we also ignore any unknown options so that we
can pass them along, too.
> repo_init_revisions(repo, &revs, prefix);
> + if (argc > 1) {
> + argc = setup_revisions(argc, argv, &revs, &s_r_opt);
> + if (argc > 1)
> + usage(_("too many arguments"));
> + }
>
> if (show_progress < 0)
> show_progress = isatty(2);
And then, if we have any additional parameters then we pass it on to
`setup_revisions()`.
> diff --git a/t/t1901-repo-structure.sh b/t/t1901-repo-structure.sh
> index 02cc2b594a..eb2c595955 100755
> --- a/t/t1901-repo-structure.sh
> +++ b/t/t1901-repo-structure.sh
> @@ -144,6 +144,90 @@ test_expect_success SHA1 'repository with references and objects' '
> )
> '
>
> +test_expect_success SHA1 'repository with references and objects, filtered' '
> + 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" &&
> + git checkout -b grobble &&
> + test_commit_bulk --ref=refs/heads/grobble 20 &&
> + git checkout master &&
> + test_commit_bulk 20 &&
> + # Also creates a commit, tree, and blob.
> + git notes add -m foo &&
> +
> + # 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 | 5 |
> + | * Branches | 2 |
> + | * Tags | 1 |
> + | * Remotes | 1 |
> + | * Others | 1 |
> + | | |
> + | * Reachable objects | |
> + | * Count | 3.06 k |
> + | * Commits | 1.05 k |
> + | * Trees | 1.01 k |
> + | * Blobs | 1.01 k |
> + | * Tags | 1 |
> + | * Inflated size | 16.04 MiB |
> + | * Commits | 226.54 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] df6400c01440c329f1011669c4c26cc0c7852887
> + [3] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c
> + [4] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c
> + [5] 97d808e45116bf02103490294d3d46dad7a2ac62
> + [6] 4dae4f5954f5e6feb3577cfb1b181daa3fd3afd2
> + EOF
> +
> + git repo structure >actual 2>actual-err &&
> + cp actual /tmp/actual &&
> + cp expect /tmp/expect &&
> + test_cmp expect actual &&
> + test_line_count = 0 actual-err &&
> +
> + git repo structure grobble ^master >actual 2>actual-err &&
> + cp actual /tmp &&
> + cp actual-err /tmp &&
> + test_grep "| \* Commits | 21 |" actual &&
> + test_grep "| \* Trees | 2 |" actual &&
> + test_grep "| \* Commits | 4.50 KiB |" actual &&
> + test_grep "| \* Trees | 32.35 KiB |" actual &&
> + test_grep "| \* Blobs | 11.68 KiB |" actual &&
> + test_line_count = 0 actual-err
> + )
> +'
I wonder whether we maybe want to have some additional tests that assert
that you can also pass e.g.:
- A tree or blob.
- Revision options, like for example `--all --filter=object:type=blob`.
To make the test a bit less repetitive we might also want to use
`--format=lines` and then only check for
"objects.*.{inflated,disk}_size" to exercise only the parts that matter
to this test.
Thanks!
Patrick
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v2 1/1] repo: add filtering options to "repo structure"
2026-10-06 5:51 ` Patrick Steinhardt
@ 2026-10-08 15:46 ` Kaartic Sivaraam
2026-10-09 15:49 ` Mark C. Chu-Carroll
0 siblings, 1 reply; 12+ messages in thread
From: Kaartic Sivaraam @ 2026-10-08 15:46 UTC (permalink / raw)
To: Patrick Steinhardt, Mark C. Chu-Carroll; +Cc: git, jltobler
On 10/6/26 11:21, Patrick Steinhardt wrote:
> On Mon, Oct 05, 2026 at 01:40:44PM -0400, Mark C. Chu-Carroll wrote:
>> Implement filtering for repo structure, imitating the mechanism
>> used in "git log".
>
> The message should give an explanation of what this change does, and
> what the motivation behind it is.
>
Indeed. The cover letter provides more context about the change. I think
it makes sense to include a significant portion of the cover letter in
the commit message. We could even likely drop the cover letter
altogether if it feels to add no value.
That said,
> repo: add filtering options to "repo structure"
I think the following may be a better commit title:
repo: add revision filtering support to "repo structure"
--
Sivaraam
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v2 1/1] repo: add filtering options to "repo structure"
2026-10-08 15:46 ` Kaartic Sivaraam
@ 2026-10-09 15:49 ` Mark C. Chu-Carroll
0 siblings, 0 replies; 12+ messages in thread
From: Mark C. Chu-Carroll @ 2026-10-09 15:49 UTC (permalink / raw)
To: Kaartic Sivaraam, Patrick Steinhardt, Mark C. Chu-Carroll; +Cc: git, jltobler
On Thu Oct 8, 2026 at 11:46 AM EDT, Kaartic Sivaraam wrote:
> On 10/6/26 11:21, Patrick Steinhardt wrote:
>> On Mon, Oct 05, 2026 at 01:40:44PM -0400, Mark C. Chu-Carroll wrote:
>>> Implement filtering for repo structure, imitating the mechanism
>>> used in "git log".
>>
>> The message should give an explanation of what this change does, and
>> what the motivation behind it is.
>>
>
> Indeed. The cover letter provides more context about the change. I think
> it makes sense to include a significant portion of the cover letter in
> the commit message. We could even likely drop the cover letter
> altogether if it feels to add no value.
>
> That said,
>
> > repo: add filtering options to "repo structure"
>
> I think the following may be a better commit title:
>
> repo: add revision filtering support to "repo structure"
You're right - that's much better.
-Mark
--
Mark Craig Chu-Carroll (@MarkChuCarroll at gitlab)
*** Software Tools/Math Geek - Software Engineer at Gitlab
*** Work Email: mcarroll@gitlab.com / markchucarroll@fastmail.com
*** Personal Blog: http://goodmath.org/blog / Personal email: markcc@gmail.com
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v3 0/1] repo: add revision filtering options to "repo structure"
2026-09-24 16:45 ` [PATCH 1/1] " Mark C. Chu-Carroll
2026-09-30 16:28 ` Patrick Steinhardt
2026-10-05 17:40 ` [PATCH v2 0/1] " Mark C. Chu-Carroll
@ 2026-10-09 18:09 ` Mark C. Chu-Carroll
2026-10-09 18:09 ` [PATCH v3 1/1] repo: add " Mark C. Chu-Carroll
2 siblings, 1 reply; 12+ messages in thread
From: Mark C. Chu-Carroll @ 2026-10-09 18:09 UTC (permalink / raw)
To: git; +Cc: jltobler, ps, Mark C. Chu-Carroll
"git repo structure" provides a collection of useful information
about the information stored in a repo. In particular, it's
valuable for diagnosing performance issues caused by large objects
stored in a repo.
The current implementation of "git repo stucture" provides summary
information about everything in the repository - all of the
branches, remotes, tags, stashes, and notes. But sometimes
to properly diagnose a problem, it's useful to be able to get
information about the specific part of the repo that's exhibiting
a problem.
Add the option to specify a set of revs. If revs are included,
then the set of objects processed will be limited based on the
rev specification; otherwise, all objects will be processed.
Mark C. Chu-Carroll (1):
repo: add revision filtering options to "repo structure"
Documentation/git-repo.adoc | 37 +++++-
Documentation/git-rev-list.adoc | 2 +-
Documentation/revisions.adoc | 2 +-
builtin/repo.c | 32 +++--
revision.c | 2 +-
revision.h | 2 +-
t/t1901-repo-structure.sh | 211 ++++++++++++++++++++++++--------
7 files changed, 217 insertions(+), 71 deletions(-)
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v3 1/1] repo: add filtering options to "repo structure"
2026-10-09 18:09 ` [PATCH v3 0/1] repo: add revision " Mark C. Chu-Carroll
@ 2026-10-09 18:09 ` Mark C. Chu-Carroll
2026-10-09 21:24 ` Junio C Hamano
0 siblings, 1 reply; 12+ messages in thread
From: Mark C. Chu-Carroll @ 2026-10-09 18:09 UTC (permalink / raw)
To: git; +Cc: jltobler, ps, Mark C. Chu-Carroll
The current implementation of "git repo stucture" provides summary
information about everything in the repository - all of the
branches, remotes, tags, stashes, and notes. But sometimes
to properly diagnose a problem, it's useful to be able to
filter this information to get focused data about the specific
part of the repo that's exhibiting a problem.
Update "git repo structure" to use rev expressions
to filter the set of objects to be processed.
This implementation makes an awkward trade-off involving
"setup_revisions". In order to accept standard the standard
revs parameters, it uses `setup_revisions`, which does a ref
walk and populates the pending objects list that will be used
by "repo structure". But this ref walk doesn't count refs in the
way that "repo structure" requires, and because "setup_revisions"
doesn't support a custom callback for its ref-walk, that's not
changeable.
So in the cases where a user specifies revs, "repo structure" does
an initial ref-walk as part of "setup_revisions", and then a second
ref-walk in "count_references" in repo.c to count by reference type.
Further, if the user doesn't specify a revs parameter, "setup_revisions"
is not called, so the "count_references" walk needs to add objects
to the pending list. A parameter is added to "count_references"
to allow it to distinguish the cases where the pending
list is already populated from the case where it needs to
populate it.
Signed-off-by: Mark C. Chu-Carroll <markchucarroll@fastmail.com>
---
Documentation/git-repo.adoc | 37 +++++-
Documentation/git-rev-list.adoc | 2 +-
Documentation/revisions.adoc | 2 +-
builtin/repo.c | 32 +++--
revision.c | 2 +-
revision.h | 2 +-
t/t1901-repo-structure.sh | 211 ++++++++++++++++++++++++--------
7 files changed, 217 insertions(+), 71 deletions(-)
diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc
index ed7d80c690..d2cbabd330 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] [<revs>...]
DESCRIPTION
-----------
@@ -56,9 +56,10 @@ supported:
`nul`:::
Similar to `lines`, but using a _NUL_ character after each value.
-`structure [--format=(table|lines|nul) | -z]`::
- Retrieve statistics about the current repository structure. The
- following kinds of information are reported:
+`structure [--format=(table|lines|nul) | -z] [<revs>...]::
++
+Retrieve statistics about the current repository structure. The
+following kinds of information are reported:
+
* Reference counts categorized by type
* Reachable object counts categorized by type
@@ -66,6 +67,12 @@ supported:
* Total disk size of reachable objects by type
* Largest reachable objects in the repository by type
+
+The set of objects counted can be filtered by specifying a collection
+of revisions to select which objects will be counted. These parameters
+follow the same syntax as the parameters to similar commands like
+`git log`. If no includes are specified, then the include set is all
+reachable objects.
++
The output format can be chosen through the flag `--format`. Three formats are
supported:
+
@@ -141,6 +148,28 @@ using the `nul` format:
git repo info --format=nul layout.bare layout.shallow
------------
+* Generates information about storage usage in the repository:
++
+------------
+git repo structure
+------------
++
+
+* Generates information about storage usage in the repository omitting
+the branch "foo":
++
+------------
+git repo structure ^foo
+------------
++
+* Generates information about repository objects reachable from
+the references "x" and "y", but omitting anything that can
+only be reached on a path including "xchild":
++
+------------
+git repo structure x y ^xchild
+------------
++
SEE ALSO
--------
linkgit:git-rev-parse[1]
diff --git a/Documentation/git-rev-list.adoc b/Documentation/git-rev-list.adoc
index f582491dd4..05f16a1146 100644
--- a/Documentation/git-rev-list.adoc
+++ b/Documentation/git-rev-list.adoc
@@ -114,7 +114,7 @@ do
done |
sort -n
----------
-
+p
* Compare the on-disk size of branches in one group of refs, excluding
another. If you co-mingle objects from multiple remotes in a single
repository, this can show which remotes are contributing to the
diff --git a/Documentation/revisions.adoc b/Documentation/revisions.adoc
index 3fbfbd3d5f..4391f8daa6 100644
--- a/Documentation/revisions.adoc
+++ b/Documentation/revisions.adoc
@@ -226,7 +226,7 @@ existing tag object.
This is most useful to address a blob or tree from a commit or tree that has
the same tree structure as the working tree.
-':[<n>:]<path>', e.g. ':0:README', ':README'::
+ ':[<n>:]<path>', e.g. ':0:README', ':README'::
A colon, optionally followed by a stage number (0 to 3) and a
colon, followed by a path, names a blob object in the
index at the given path. A missing stage number (and the colon
diff --git a/builtin/repo.c b/builtin/repo.c
index 84e012f83f..081940a85c 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] [<revs>...]"
static const char *const repo_usage[] = {
REPO_INFO_USAGE,
@@ -740,6 +740,7 @@ struct count_references_data {
struct ref_stats *stats;
struct rev_info *revs;
struct progress *progress;
+ bool add_to_pending;
};
static int count_references(const struct reference *ref, void *cb_data)
@@ -766,10 +767,12 @@ static int count_references(const struct reference *ref, void *cb_data)
}
/*
- * While iterating through references for counting, also add OIDs in
+ * While iterating through references for counting, if we didn't already
+ * populate the pending list in setup_revisions, also add OIDs in
* preparation for the path walk.
*/
- add_pending_oid(data->revs, NULL, ref->oid, 0);
+ if (data->add_to_pending)
+ add_pending_oid(data->revs, NULL, ref->oid, 0);
ref_count = get_total_reference_count(stats);
display_progress(data->progress, ref_count);
@@ -780,11 +783,13 @@ static int count_references(const struct reference *ref, void *cb_data)
static void structure_count_references(struct ref_stats *stats,
struct rev_info *revs,
struct repository *repo,
- int show_progress)
+ int show_progress,
+ bool add_to_pending)
{
struct count_references_data data = {
.stats = stats,
.revs = revs,
+ .add_to_pending = add_to_pending
};
if (show_progress)
@@ -935,6 +940,7 @@ 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;
+ bool included_revision_args = false;
struct option options[] = {
OPT_CALLBACK_F(0, "format", &format, N_("format"),
N_("output format"),
@@ -947,16 +953,20 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
OPT_END()
};
- argc = parse_options(argc, argv, prefix, options, repo_structure_usage, 0);
- if (argc)
- usage(_("too many arguments"));
-
- repo_init_revisions(repo, &revs, prefix);
-
+ argc = parse_options(argc, argv, prefix, options, repo_structure_usage,
+ PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);
if (show_progress < 0)
show_progress = isatty(2);
- structure_count_references(&stats.refs, &revs, repo, show_progress);
+ repo_init_revisions(repo, &revs, prefix);
+ if (argc > 1) {
+ argc = setup_revisions(argc, argv, &revs, NULL);
+ included_revision_args = true;
+ }
+ if (argc > 1)
+ usage(_("too many arguments"));
+
+ structure_count_references(&stats.refs, &revs, repo, show_progress, !included_revision_args);
structure_count_objects(&stats.objects, &revs, repo, show_progress);
switch (format) {
diff --git a/revision.c b/revision.c
index ee1df92d1d..79d44b58b5 100644
--- a/revision.c
+++ b/revision.c
@@ -2837,7 +2837,7 @@ static int handle_revision_pseudo_opt(struct rev_info *revs,
* NOTE!
*
* Commands like "git shortlog" will not accept the options below
- * unless parse_revision_opt queues them (as opposed to erroring
+ * unless parse_revision_op t queues them (as opposed to erroring
* out).
*
* When implementing your new pseudo-option, remember to
diff --git a/revision.h b/revision.h
index e5dabd18ce..63135c5f88 100644
--- a/revision.h
+++ b/revision.h
@@ -125,7 +125,7 @@ struct topo_walk_info;
struct rev_info {
/*
- * Work queue of commits, stored as either a linked list or a
+~ * Work queue of commits, stored as either a linked list or a
* priority queue, but never both at the same time.
* rev_info_commit_list_to_queue() converts list to queue.
*/
diff --git a/t/t1901-repo-structure.sh b/t/t1901-repo-structure.sh
index 02cc2b594a..e35b46a277 100755
--- a/t/t1901-repo-structure.sh
+++ b/t/t1901-repo-structure.sh
@@ -21,58 +21,6 @@ object_type_disk_usage() {
fi
}
-test_expect_success 'empty repository' '
- test_when_finished "rm -rf repo" &&
- git init repo &&
- (
- cd repo &&
- cat >expect <<-\EOF &&
- | Repository structure | Value |
- | ------------------------- | ------ |
- | * References | |
- | * Count | 0 |
- | * Branches | 0 |
- | * Tags | 0 |
- | * Remotes | 0 |
- | * Others | 0 |
- | | |
- | * Reachable objects | |
- | * Count | 0 |
- | * Commits | 0 |
- | * Trees | 0 |
- | * Blobs | 0 |
- | * Tags | 0 |
- | * Inflated size | 0 B |
- | * Commits | 0 B |
- | * Trees | 0 B |
- | * Blobs | 0 B |
- | * Tags | 0 B |
- | * Disk size | 0 B |
- | * Commits | 0 B |
- | * Trees | 0 B |
- | * Blobs | 0 B |
- | * Tags | 0 B |
- | | |
- | * Largest objects | |
- | * Commits | |
- | * Maximum size | 0 B |
- | * Maximum parents | 0 |
- | * Trees | |
- | * Maximum size | 0 B |
- | * Maximum entries | 0 |
- | * Blobs | |
- | * Maximum size | 0 B |
- | * Tags | |
- | * Maximum size | 0 B |
- 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 &&
@@ -144,6 +92,165 @@ test_expect_success SHA1 'repository with references and objects' '
)
'
+test_expect_success SHA1 'repository with branches' '
+ 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" &&
+ git checkout -b grobble &&
+ test_commit_bulk --ref=refs/heads/grobble 20 &&
+ git checkout master &&
+ test_commit_bulk 20 &&
+ # Also creates a commit, tree, and blob.
+ git notes add -m foo &&
+ git repo structure --format=lines >actual 2>actual-err &&
+ test_line_count = 0 actual-err &&
+ test_grep "objects.commits.inflated.size=231976" actual &&
+ test_grep "objects.commits.count=1046" actual &&
+ test_grep "objects.blobs.count=1006" actual &&
+ test_grep "objects.trees.count=1006" actual &&
+ test_grep "objects.trees.disk_size=284547" actual &&
+ test_grep "objects.blobs.disk_size=21022" actual &&
+ test_grep "objects.tags.disk_size=126" actual &&
+ test_line_count = 0 actual-err
+ )
+'
+
+test_expect_success SHA1 'filter by a positive rev' '
+ 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" &&
+ git checkout -b grobble &&
+ test_commit_bulk --ref=refs/heads/grobble 20 &&
+ git checkout master &&
+ test_commit_bulk 20 &&
+ git notes add -m foo &&
+
+ git repo structure --format=lines grobble >actual 2>actual-err &&
+ test_grep "objects.commits.count=1025" actual &&
+ test_grep "objects.commits.disk_size=160455" actual &&
+ test_grep "objects.commits.inflated.size=227371" actual &&
+ test_grep "objects.blobs.count=1005" actual &&
+ test_grep "objects.blobs.disk_size=21003" actual &&
+ test_grep "objects.blobs.inflated_size=11958" actual &&
+ test_grep "objects.trees.count=1005" actual &&
+ test_grep "objects.trees.disk_size=284462" actual &&
+ test_grep "objects.trees.inflated_size=16578363" actual &&
+ test_grep "objects.tags.disk_size=0" actual &&
+ test_line_count = 0 actual-err
+ )
+'
+
+test_expect_success SHA1 'filter by both positive and negative revs' '
+ 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" &&
+ git checkout -b grobble &&
+ test_commit_bulk --ref=refs/heads/grobble 20 &&
+ git checkout master &&
+ test_commit_bulk 20 &&
+ # Also creates a commit, tree, and blob.
+ git notes add -m foo &&
+ git repo structure --format=lines >actual 2>actual-err &&
+ test_line_count = 0 actual-err &&
+
+ git repo structure --format=lines grobble ^master >actual 2>actual-err &&
+ test_grep "objects.commits.count=20" actual &&
+ test_grep "objects.commits.disk_size=3122" actual &&
+ test_grep "objects.commits.inflated.size=4411" actual &&
+ test_grep "objects.blobs.count=1005" actual &&
+ test_grep "objects.blobs.disk_size=21003" actual &&
+ test_grep "objects.blobs.inflated_size=11958" actual &&
+ test_grep "objects.trees.count=1" actual &&
+ test_grep "objects.trees.disk_size=53" actual &&
+ test_grep "objects.trees.inflated_size=33063" actual &&
+ test_grep "objects.tags.disk_size=0" actual &&
+ test_line_count = 0 actual-err
+ )
+'
+
+test_expect_success 'filter by object type' '
+ 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" &&
+ git checkout -b grobble &&
+ test_commit_bulk --ref=refs/heads/grobble 20 &&
+ git checkout master &&
+ test_commit_bulk 20 &&
+ # Also creates a commit, tree, and blob.
+ git notes add -m foo &&
+ git repo structure --format=lines --objects --all --filter=object:type=blob >actual 2>actual-err &&
+ cp actual /tmp/test-actual-exclude-blob &&
+ test_grep "objects.commits.disk_size=0" actual &&
+ test_grep "objects.commits.max_size=0" actual &&
+ test_grep "objects.tags.disk_size=0" actual &&
+ test_grep "objects.tags.inflated_size=0" actual &&
+ test_grep "objects.trees.disk_size=0" actual &&
+ test_grep "objects.trees.inflated_size=0" actual &&
+ test_grep "objects.blobs.disk_size=21022" actual &&
+ test_grep "objects.blobs.count=1006" actual &&
+ test_grep "objects.blobs.disk_size=21022" actual &&
+ test_grep "objects.blobs.inflated_size=11962" actual &&
+ test_line_count = 0 actual-err
+ )
+'
+
+test_expect_success 'filter using by ref^{tree}' '
+ 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" &&
+ git checkout -b grobble &&
+ test_commit_bulk --ref=refs/heads/grobble 20 &&
+ git checkout master &&
+ test_commit_bulk 20 &&
+ # Also creates a commit, tree, and blob.
+ git notes add -m foo &&
+ git repo structure --format=lines HEAD^{tree} >actual 2>err &&
+ test_grep "objects.commits.count=0" actual &&
+ test_grep "objects.trees.count=1" actual &&
+ test_grep "objects.blobs.count=1005" actual &&
+ test_grep "objects.tags.count=0" actual &&
+ test_grep "objects.commits.inflated_size=0" actual &&
+ test_grep "objects.trees.inflated_size=33063" actual &&
+ test_grep "objects.blobs.inflated_size=11958" actual &&
+ test_grep "objects.tags.inflated_size=0" actual &&
+ test_grep "objects.commits.disk_size=0" actual &&
+ test_grep "objects.trees.disk_size=53" actual &&
+ test_grep "objects.blobs.disk_size=21003" actual &&
+ test_grep "objects.tags.disk_size=0" actual &&
+ test_line_count = 0 err
+ )
+'
+
test_expect_success SHA1 'lines and nul format' '
test_when_finished "rm -rf repo" &&
git init repo &&
--
2.53.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH v3 1/1] repo: add filtering options to "repo structure"
2026-10-09 18:09 ` [PATCH v3 1/1] repo: add " Mark C. Chu-Carroll
@ 2026-10-09 21:24 ` Junio C Hamano
0 siblings, 0 replies; 12+ messages in thread
From: Junio C Hamano @ 2026-10-09 21:24 UTC (permalink / raw)
To: Mark C. Chu-Carroll; +Cc: git, jltobler, ps
"Mark C. Chu-Carroll" <markchucarroll@fastmail.com> writes:
> diff --git a/revision.c b/revision.c
> index ee1df92d1d..79d44b58b5 100644
> --- a/revision.c
> +++ b/revision.c
> @@ -2837,7 +2837,7 @@ static int handle_revision_pseudo_opt(struct rev_info *revs,
> * NOTE!
> *
> * Commands like "git shortlog" will not accept the options below
> - * unless parse_revision_opt queues them (as opposed to erroring
> + * unless parse_revision_op t queues them (as opposed to erroring
> * out).
> *
> * When implementing your new pseudo-option, remember to
What is this change about?
> diff --git a/revision.h b/revision.h
> index e5dabd18ce..63135c5f88 100644
> --- a/revision.h
> +++ b/revision.h
> @@ -125,7 +125,7 @@ struct topo_walk_info;
>
> struct rev_info {
> /*
> - * Work queue of commits, stored as either a linked list or a
> +~ * Work queue of commits, stored as either a linked list or a
> * priority queue, but never both at the same time.
> * rev_info_commit_list_to_queue() converts list to queue.
> */
Ditto.
Everybody makes mistakes during their editing, and occasionally fat
thumb hits unintended keys while the cursor is in an area one is not
editing at all. Mistakes happen and that is perfectly OK.
But a hunk like this one in a submitted patch is a clear sign that
even the author is not reading what they are sending out. And this
patch, among its 16 hunks, two are such hunks that was never
proofread.
Quite honestly, it is beyond me how anybody would expect others to
seriously take their time to review such a patch.
Grumble.
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-10-09 21:24 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-24 16:45 [PATCH 0/1] repo: add filtering options to "repo structure" Mark C. Chu-Carroll
2026-09-24 16:45 ` [PATCH 1/1] " Mark C. Chu-Carroll
2026-09-30 16:28 ` Patrick Steinhardt
2026-10-05 18:52 ` Mark C. Chu-Carroll
2026-10-05 17:40 ` [PATCH v2 0/1] " Mark C. Chu-Carroll
2026-10-05 17:40 ` [PATCH v2 1/1] " Mark C. Chu-Carroll
2026-10-06 5:51 ` Patrick Steinhardt
2026-10-08 15:46 ` Kaartic Sivaraam
2026-10-09 15:49 ` Mark C. Chu-Carroll
2026-10-09 18:09 ` [PATCH v3 0/1] repo: add revision " Mark C. Chu-Carroll
2026-10-09 18:09 ` [PATCH v3 1/1] repo: add " Mark C. Chu-Carroll
2026-10-09 21:24 ` Junio C Hamano
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox