Turn git survey into a deprecated shim over git repo structure (#6268)

`git survey` was always experimental, and I never got around to
upstreaming it to make it non-experimental.

In the meantime, the `git repo structure` command was upstreamed
upstream, which covers most of the same ground with a cleaner option
surface and a stable output contract. This PR closes the remaining gap
(annotated-tag breakdown, ref scoping, top-N paths by
count/disk/inflated, and the corresponding configuration knob) and then
turns `git survey` into a thin shim that warns about deprecation,
translates its old command line into the equivalent `git repo structure`
invocation, and re-execs the canonical command. Net result: one
user-facing tool to maintain and to teach instead of two.

The intent is that scripts pinned to `git survey` keep working (a
warning aside), and that operators have a single answer when they ask
"how do I see what's making my repository large?". The `survey.*`
configuration keys are intentionally dropped; the only one that
mattered, `survey.top`, has a direct replacement in
`repo.structure.top`.
This commit is contained in:
Johannes Schindelin
2026-07-08 12:17:12 +02:00
committed by Git for Windows Build Agent
8 changed files with 579 additions and 999 deletions

View File

@@ -515,6 +515,8 @@ include::config/remotes.adoc[]
include::config/repack.adoc[]
include::config/repo.adoc[]
include::config/rerere.adoc[]
include::config/revert.adoc[]

View File

@@ -0,0 +1,11 @@
repo.structure.*::
These variables adjust the default behavior of the
`git repo structure` command.
+
--
top::
This integer value implies `--top=<N>`, specifying the
number of largest paths to report in each detail table.
Must be non-negative; defaults to `0`, which disables the
detail tables.
--

View File

@@ -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 [<options>]
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`::
Retrieve statistics about the current repository structure. The
following kinds of information are reported:
+
@@ -65,6 +65,32 @@ supported:
* Total inflated size of reachable objects by type
* Total disk size of reachable objects by type
* Largest reachable objects in the repository by type
* Optionally, the top-_N_ largest paths by count, on-disk size, and
inflated size (see `--top` below)
+
By default every reference enumerated by `for-each-ref` contributes to
the counts and object walk. Use `--ref-filter` to narrow the scope.
`--ref-filter=<pattern>`;;
Only count references whose full name matches one of the
given <pattern>s, and only seed the object walk from
those references. The option is repeatable; multiple
patterns form a union. Patterns use the same `wildmatch`
semantics as `git for-each-ref`'s positional arguments,
so `*` does not cross `/`. Examples:
`--ref-filter='refs/heads/*'` for local branches,
`--ref-filter='refs/tags/v*'` for release tags,
`--ref-filter='refs/remotes/origin/*'` for a single
remote's branches.
`--top=<n>`;;
Also report the _n_ largest paths in the repository,
separately for trees and blobs and separately ranked by
object count, on-disk size, and inflated size. Defaults
to `0`, which suppresses the detail tables. The default
can also be set via the `repo.structure.top` configuration
variable; an explicit `--top=<n>` on the command line
overrides the configured value.
+
The output format can be chosen through the flag `--format`. Three formats are
supported:

View File

@@ -3,16 +3,23 @@ git-survey(1)
NAME
----
git-survey - EXPERIMENTAL: Measure various repository dimensions of scale
git-survey - DEPRECATED: Measure various repository dimensions of scale
SYNOPSIS
--------
[verse]
(EXPERIMENTAL!) 'git survey' <options>
(DEPRECATED!) 'git survey' <options>
DESCRIPTION
-----------
NOTE: `git survey` is being superseded by `git repo structure`. New
deployments and new features should use `git repo structure`; its
`--ref-filter=<pattern>` option subsumes the various `--branches`,
`--tags`, and `--remotes` flags here, and `--top=<N>` provides the
same detail tables. During the deprecation phase, `git survey` is
a thin shim over `git repo structure`. See linkgit:git-repo[1].
Survey the repository and measure various dimensions of scale.
As repositories grow to "monorepo" size, certain data shapes can cause

View File

@@ -2,6 +2,7 @@
#include "builtin.h"
#include "commit.h"
#include "config.h"
#include "environment.h"
#include "hash.h"
#include "hex.h"
@@ -21,13 +22,14 @@
#include "tree.h"
#include "tree-walk.h"
#include "utf8.h"
#include "wildmatch.h"
#define REPO_INFO_USAGE \
"git repo info [--format=(lines|nul) | -z] [--all | <key>...]", \
"git repo info --keys [--format=(lines|nul) | -z]"
#define REPO_STRUCTURE_USAGE \
"git repo structure [--format=(table|lines|nul) | -z]"
"git repo structure [<options>]"
static const char *const repo_usage[] = {
REPO_INFO_USAGE,
@@ -315,10 +317,45 @@ struct largest_objects {
struct object_data tree_entries;
};
/*
* Per-path summary of all objects that share a given (path, type) under the
* path-walk traversal: the count of objects, their on-disk size, and their
* inflated size.
*/
struct path_size_summary {
char *path;
size_t nr;
size_t disk_size;
size_t inflated_size;
};
typedef int (*path_summary_cmp)(const struct path_size_summary *,
const struct path_size_summary *);
/*
* A bounded, descending-sorted list of the largest summaries seen so far,
* with a fixed comparison function defining "largest". New summaries are
* inserted with maybe_insert_into_top_paths(); smaller ones fall off the
* end of the list.
*/
struct top_paths_table {
path_summary_cmp cmp_fn;
size_t nr;
size_t alloc;
struct path_size_summary *data;
};
struct top_paths {
struct top_paths_table by_count;
struct top_paths_table by_disk;
struct top_paths_table by_inflated;
};
struct ref_stats {
size_t branches;
size_t remotes;
size_t tags;
size_t annotated_tags;
size_t others;
};
@@ -334,6 +371,8 @@ struct object_stats {
struct object_values inflated_sizes;
struct object_values disk_sizes;
struct largest_objects largest;
struct top_paths top_trees;
struct top_paths top_blobs;
};
struct repo_structure {
@@ -504,6 +543,8 @@ static void stats_table_setup_structure(struct stats_table *table,
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->annotated_tags,
" * %s", _("Annotated"));
stats_table_count_addf(table, refs->remotes, " * %s", _("Remotes"));
stats_table_count_addf(table, refs->others, " * %s", _("Others"));
@@ -576,6 +617,41 @@ static void stats_table_setup_structure(struct stats_table *table,
" * %s", _("Maximum size"));
}
static void stats_table_add_top_paths(struct stats_table *table,
const struct top_paths *top,
const char *header)
{
if (!top->by_count.nr && !top->by_disk.nr && !top->by_inflated.nr)
return;
stats_table_addf(table, "");
stats_table_addf(table, "* %s", header);
stats_table_addf(table, " * %s", _("Top by count"));
for (size_t i = 0; i < top->by_count.nr; i++)
stats_table_count_addf(table, top->by_count.data[i].nr,
" * %s", top->by_count.data[i].path);
stats_table_addf(table, " * %s", _("Top by disk size"));
for (size_t i = 0; i < top->by_disk.nr; i++)
stats_table_size_addf(table, top->by_disk.data[i].disk_size,
" * %s", top->by_disk.data[i].path);
stats_table_addf(table, " * %s", _("Top by inflated size"));
for (size_t i = 0; i < top->by_inflated.nr; i++)
stats_table_size_addf(table,
top->by_inflated.data[i].inflated_size,
" * %s",
top->by_inflated.data[i].path);
}
static void stats_table_setup_top_paths(struct stats_table *table,
struct object_stats *objects)
{
stats_table_add_top_paths(table, &objects->top_trees, _("Top trees"));
stats_table_add_top_paths(table, &objects->top_blobs, _("Top blobs"));
}
#define INDEX_WIDTH 4
static void stats_table_print_structure(const struct stats_table *table)
@@ -680,6 +756,54 @@ static void print_object_data(const char *key, char key_delim,
value_delim);
}
static void print_keyvalue_path(const char *key, char key_delim,
const char *path, char value_delim)
{
printf("%s%c", key, key_delim);
if (key_delim == '=')
quote_c_style(path, NULL, stdout, 0);
else
fputs(path, stdout);
fputc(value_delim, stdout);
}
static void top_paths_keyvalue_print(const char *prefix,
const struct top_paths *top,
char key_delim, char value_delim)
{
for (size_t i = 0; i < top->by_count.nr; i++) {
printf("%s.by_count.%" PRIuMAX ".",
prefix, (uintmax_t)(i + 1));
print_keyvalue_path("path", key_delim,
top->by_count.data[i].path, value_delim);
printf("%s.by_count.%" PRIuMAX ".",
prefix, (uintmax_t)(i + 1));
print_keyvalue("count", key_delim,
top->by_count.data[i].nr, value_delim);
}
for (size_t i = 0; i < top->by_disk.nr; i++) {
printf("%s.by_disk_size.%" PRIuMAX ".",
prefix, (uintmax_t)(i + 1));
print_keyvalue_path("path", key_delim,
top->by_disk.data[i].path, value_delim);
printf("%s.by_disk_size.%" PRIuMAX ".",
prefix, (uintmax_t)(i + 1));
print_keyvalue("disk_size", key_delim,
top->by_disk.data[i].disk_size, value_delim);
}
for (size_t i = 0; i < top->by_inflated.nr; i++) {
printf("%s.by_inflated_size.%" PRIuMAX ".",
prefix, (uintmax_t)(i + 1));
print_keyvalue_path("path", key_delim,
top->by_inflated.data[i].path, value_delim);
printf("%s.by_inflated_size.%" PRIuMAX ".",
prefix, (uintmax_t)(i + 1));
print_keyvalue("inflated_size", key_delim,
top->by_inflated.data[i].inflated_size,
value_delim);
}
}
static void structure_keyvalue_print(struct repo_structure *stats,
char key_delim, char value_delim)
{
@@ -687,6 +811,8 @@ static void structure_keyvalue_print(struct repo_structure *stats,
stats->refs.branches, value_delim);
print_keyvalue("references.tags.count", key_delim,
stats->refs.tags, value_delim);
print_keyvalue("references.tags.annotated.count", key_delim,
stats->refs.annotated_tags, value_delim);
print_keyvalue("references.remotes.count", key_delim,
stats->refs.remotes, value_delim);
print_keyvalue("references.others.count", key_delim,
@@ -733,21 +859,50 @@ static void structure_keyvalue_print(struct repo_structure *stats,
print_object_data("objects.trees.max_entries", key_delim,
&stats->objects.largest.tree_entries, value_delim);
top_paths_keyvalue_print("objects.trees.top", &stats->objects.top_trees,
key_delim, value_delim);
top_paths_keyvalue_print("objects.blobs.top", &stats->objects.top_blobs,
key_delim, value_delim);
fflush(stdout);
}
struct count_references_data {
struct ref_stats *stats;
struct rev_info *revs;
struct repository *repo;
const struct string_list *filters;
struct progress *progress;
};
static int ref_matches_any_filter(const char *refname,
const struct string_list *filters)
{
if (!filters->nr)
return 1;
for (size_t i = 0, namelen = strlen(refname); i < filters->nr; i++) {
const char *p = filters->items[i].string;
size_t plen = strlen(p);
if (plen <= namelen &&
!strncmp(refname, p, plen) &&
(refname[plen] == '\0' || refname[plen] == '/' ||
(plen && p[plen - 1] == '/')))
return 1;
if (!wildmatch(p, refname, WM_PATHNAME))
return 1;
}
return 0;
}
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;
if (!ref_matches_any_filter(ref->name, data->filters))
return 0;
switch (ref_kind_from_refname(ref->name)) {
case FILTER_REFS_BRANCHES:
stats->branches++;
@@ -757,6 +912,9 @@ static int count_references(const struct reference *ref, void *cb_data)
break;
case FILTER_REFS_TAGS:
stats->tags++;
if (odb_read_object_info(data->repo->objects,
ref->oid, NULL) == OBJ_TAG)
stats->annotated_tags++;
break;
case FILTER_REFS_OTHERS:
stats->others++;
@@ -780,11 +938,14 @@ 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,
const struct string_list *filters,
int show_progress)
{
struct count_references_data data = {
.stats = stats,
.revs = revs,
.repo = repo,
.filters = filters,
};
if (show_progress)
@@ -799,8 +960,88 @@ struct count_objects_data {
struct object_database *odb;
struct object_stats *stats;
struct progress *progress;
size_t top_nr;
};
static int cmp_by_nr(const struct path_size_summary *s1,
const struct path_size_summary *s2)
{
return (s1->nr > s2->nr) - (s1->nr < s2->nr);
}
static int cmp_by_disk_size(const struct path_size_summary *s1,
const struct path_size_summary *s2)
{
return (s1->disk_size > s2->disk_size) -
(s1->disk_size < s2->disk_size);
}
static int cmp_by_inflated_size(const struct path_size_summary *s1,
const struct path_size_summary *s2)
{
return (s1->inflated_size > s2->inflated_size) -
(s1->inflated_size < s2->inflated_size);
}
static void init_top_paths_table(struct top_paths_table *top, size_t limit,
path_summary_cmp cmp)
{
top->cmp_fn = cmp;
top->alloc = limit;
top->nr = 0;
CALLOC_ARRAY(top->data, limit);
}
static void init_top_paths(struct top_paths *top, size_t limit)
{
init_top_paths_table(&top->by_count, limit, cmp_by_nr);
init_top_paths_table(&top->by_disk, limit, cmp_by_disk_size);
init_top_paths_table(&top->by_inflated, limit, cmp_by_inflated_size);
}
static void clear_top_paths_table(struct top_paths_table *top)
{
for (size_t i = 0; i < top->nr; i++)
free(top->data[i].path);
free(top->data);
}
static void clear_top_paths(struct top_paths *top)
{
clear_top_paths_table(&top->by_count);
clear_top_paths_table(&top->by_disk);
clear_top_paths_table(&top->by_inflated);
}
/*
* Insert 'summary' into 'top' if it ranks among the top alloc entries by the
* table's comparator. The list is kept sorted from largest (index 0) to
* smallest. If the table is already full, the smallest entry is evicted to
* make room.
*/
static void maybe_insert_into_top_paths(struct top_paths_table *top,
const struct path_size_summary *summary)
{
size_t pos = top->nr;
while (pos > 0 && top->cmp_fn(&top->data[pos - 1], summary) < 0)
pos--;
if (pos >= top->alloc)
return;
if (top->nr == top->alloc)
free(top->data[top->nr - 1].path);
else
top->nr++;
for (size_t i = top->nr - 1; i > pos; i--)
top->data[i] = top->data[i - 1];
top->data[pos] = *summary;
top->data[pos].path = xstrdup(summary->path);
}
static void check_largest(struct object_data *data, struct object_id *oid,
size_t value)
{
@@ -824,11 +1065,12 @@ static size_t count_tree_entries(struct object *obj)
return count;
}
static int count_objects(const char *path UNUSED, struct oid_array *oids,
static int count_objects(const char *path, struct oid_array *oids,
enum object_type type, void *cb_data)
{
struct count_objects_data *data = cb_data;
struct object_stats *stats = data->stats;
struct path_size_summary summary = { .path = (char *)path };
size_t object_count;
for (size_t i = 0; i < oids->nr; i++) {
@@ -852,6 +1094,10 @@ static int count_objects(const char *path UNUSED, struct oid_array *oids,
obj = parse_object_buffer(the_repository, &oids->oid[i], type,
inflated, content, &eaten);
summary.nr++;
summary.disk_size += disk;
summary.inflated_size += inflated;
switch (type) {
case OBJ_TAG:
stats->type_counts.tags++;
@@ -894,6 +1140,22 @@ static int count_objects(const char *path UNUSED, struct oid_array *oids,
free(content);
}
if (data->top_nr) {
struct top_paths *top = NULL;
if (type == OBJ_TREE)
top = &stats->top_trees;
else if (type == OBJ_BLOB)
top = &stats->top_blobs;
if (top) {
maybe_insert_into_top_paths(&top->by_count, &summary);
maybe_insert_into_top_paths(&top->by_disk, &summary);
maybe_insert_into_top_paths(&top->by_inflated,
&summary);
}
}
object_count = get_total_object_values(&stats->type_counts);
display_progress(data->progress, object_count);
@@ -902,12 +1164,14 @@ static int count_objects(const char *path UNUSED, struct oid_array *oids,
static void structure_count_objects(struct object_stats *stats,
struct rev_info *revs,
struct repository *repo, int show_progress)
struct repository *repo, size_t top_nr,
int show_progress)
{
struct path_walk_info info = PATH_WALK_INFO_INIT;
struct count_objects_data data = {
.odb = repo->objects,
.stats = stats,
.top_nr = top_nr,
};
info.revs = revs;
@@ -922,6 +1186,22 @@ static void structure_count_objects(struct object_stats *stats,
stop_progress(&data.progress);
}
static int repo_structure_config_cb(const char *var, const char *value,
const struct config_context *cctx,
void *cb)
{
int *top_nr = cb;
if (!strcmp(var, "repo.structure.top")) {
*top_nr = git_config_int(var, value, cctx->kvi);
if (*top_nr < 0)
die(_("repo.structure.top must be non-negative"));
return 0;
}
return git_default_config(var, value, cctx, NULL);
}
static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
struct repository *repo)
{
@@ -933,6 +1213,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 top_nr = 0;
struct string_list ref_filters = STRING_LIST_INIT_DUP;
struct option options[] = {
OPT_CALLBACK_F(0, "format", &format, N_("format"),
N_("output format"),
@@ -942,24 +1224,42 @@ 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_STRING_LIST(0, "ref-filter", &ref_filters, N_("pattern"),
N_("only count refs matching <pattern>; "
"repeat to union multiple patterns")),
OPT_INTEGER(0, "top", &top_nr,
N_("report the top <n> largest paths "
"per category")),
OPT_END()
};
repo_config(repo, repo_structure_config_cb, &top_nr);
argc = parse_options(argc, argv, prefix, options, repo_structure_usage, 0);
if (argc)
usage(_("too many arguments"));
if (top_nr < 0)
die(_("--top=<n> must be non-negative"));
repo_init_revisions(repo, &revs, prefix);
if (show_progress < 0)
show_progress = isatty(2);
structure_count_references(&stats.refs, &revs, repo, show_progress);
structure_count_objects(&stats.objects, &revs, repo, show_progress);
if (top_nr) {
init_top_paths(&stats.objects.top_trees, top_nr);
init_top_paths(&stats.objects.top_blobs, top_nr);
}
structure_count_references(&stats.refs, &revs, repo, &ref_filters,
show_progress);
structure_count_objects(&stats.objects, &revs, repo, top_nr,
show_progress);
switch (format) {
case FORMAT_TABLE:
stats_table_setup_structure(&table, &stats);
stats_table_setup_top_paths(&table, &stats.objects);
stats_table_print_structure(&table);
break;
case FORMAT_NEWLINE_TERMINATED:
@@ -973,6 +1273,11 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
}
stats_table_clear(&table);
string_list_clear(&ref_filters, 0);
if (top_nr) {
clear_top_paths(&stats.objects.top_trees);
clear_top_paths(&stats.objects.top_blobs);
}
release_revisions(&revs);
return 0;

File diff suppressed because it is too large Load Diff

View File

@@ -33,6 +33,7 @@ test_expect_success 'empty repository' '
| * Count | 0 |
| * Branches | 0 |
| * Tags | 0 |
| * Annotated | 0 |
| * Remotes | 0 |
| * Others | 0 |
| | |
@@ -97,6 +98,7 @@ test_expect_success SHA1 'repository with references and objects' '
| * Count | 4 |
| * Branches | 1 |
| * Tags | 1 |
| * Annotated | 1 |
| * Remotes | 1 |
| * Others | 1 |
| | |
@@ -150,11 +152,13 @@ test_expect_success SHA1 'lines and nul format' '
(
cd repo &&
test_commit_bulk 42 &&
git tag lightweight-tag-is-not-counted-as-annotated &&
git tag -a foo -m bar &&
cat >expect <<-EOF &&
references.branches.count=1
references.tags.count=1
references.tags.count=2
references.tags.annotated.count=1
references.remotes.count=0
references.others.count=0
objects.commits.count=42
@@ -224,6 +228,108 @@ test_expect_success 'progress meter option' '
)
'
test_expect_success '--ref-filter narrows the set of refs' '
test_when_finished "rm -rf repo" &&
git init repo &&
(
cd repo &&
test_commit foo &&
git tag v1 &&
git update-ref refs/remotes/origin/main HEAD &&
git repo structure --format=lines \
--ref-filter="refs/heads/*" >out &&
grep "^references.branches.count=1$" out &&
grep "^references.tags.count=0$" out &&
grep "^references.remotes.count=0$" out
)
'
test_expect_success '--ref-filter unions multiple patterns' '
test_when_finished "rm -rf repo" &&
git init repo &&
(
cd repo &&
test_commit foo &&
git tag v1 &&
git update-ref refs/remotes/origin/main HEAD &&
git repo structure --format=lines \
--ref-filter="refs/heads/*" \
--ref-filter="refs/tags/*" >out &&
grep "^references.branches.count=1$" out &&
grep "^references.tags.count=2$" out &&
grep "^references.remotes.count=0$" out
)
'
test_expect_success '--top omitted: no top.* keys' '
test_when_finished "rm -rf repo" &&
git init repo &&
(
cd repo &&
test_commit foo &&
git repo structure --format=lines >out &&
! grep "\.top\." out
)
'
test_expect_success '--top=N reports the N largest paths per axis' '
test_when_finished "rm -rf repo" &&
git init repo &&
(
cd repo &&
mkdir -p dir1 dir2 &&
echo small >dir1/small.txt &&
printf "%010000d" 0 >dir2/big.txt &&
git add . &&
test_tick &&
git commit -m commit &&
git repo structure --format=lines --top=2 >out &&
# Two ranked entries on each axis for both types.
for axis in by_count by_disk_size by_inflated_size
do
for type in trees blobs
do
key=objects.${type}.top.${axis} &&
grep -E "^${key}\.1\.path=" out &&
grep -E "^${key}\.2\.path=" out &&
! grep -E "^${key}\.3\." out || return 1
done
done &&
# The big blob outranks the small one on disk and inflated.
key=objects.blobs.top &&
grep "^${key}.by_disk_size.1.path=dir2/big.txt$" out &&
grep "^${key}.by_inflated_size.1.path=dir2/big.txt$" out
)
'
test_expect_success '--top rejects negative values' '
test_must_fail git repo structure --top=-1 2>err &&
test_grep "must be non-negative" err
'
test_expect_success 'repo.structure.top supplies the default for --top' '
test_when_finished "rm -rf repo" &&
git init repo &&
(
cd repo &&
test_commit foo &&
git -c repo.structure.top=2 \
repo structure --format=lines >with-config &&
grep "^objects.blobs.top.by_count.1.path=" with-config &&
git -c repo.structure.top=2 \
repo structure --format=lines --top=0 >cli-override &&
! grep "\.top\." cli-override
)
'
test_expect_success 'git repo structure -h shows only repo structure usage' '
test_must_fail git repo structure -h >actual &&
test_grep "git repo structure" actual &&

View File

@@ -1,6 +1,6 @@
#!/bin/sh
test_description='git survey'
test_description='git survey (deprecated shim over `git repo structure`)'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
@@ -10,9 +10,9 @@ export TEST_PASSES_SANITIZE_LEAK
. ./test-lib.sh
test_expect_success 'git survey -h shows experimental warning' '
test_expect_success 'git survey -h shows the deprecated banner' '
test_expect_code 129 git survey -h >usage &&
grep "EXPERIMENTAL!" usage
grep "DEPRECATED!" usage
'
test_expect_success 'create a semi-interesting repo' '
@@ -25,84 +25,28 @@ test_expect_success 'create a semi-interesting repo' '
git update-ref -d refs/tags/two
'
test_expect_success 'git survey --progress' '
GIT_PROGRESS_DELAY=0 git survey --all-refs --progress >out 2>err &&
grep "Preparing object walk" err
test_expect_success 'survey prints a deprecation warning' '
git survey --all-refs >out 2>err &&
grep "is deprecated" err
'
approximate_sizes() {
# very simplistic approximate rounding
sed -Ee "s/ *(1[0-9][0-9])( |$)/ ~0.1kB\2/g" \
-e "s/ *(4[6-9][0-9]|5[0-6][0-9])( |$)/ ~0.5kB\2/g" \
-e "s/ *(5[6-9][0-9]|6[0-6][0-9])( |$)/ ~0.6kB\2/g" \
-e "s/ *1(4[89][0-9]|5[0-8][0-9])( |$)/ ~1.5kB\2/g" \
-e "s/ *1(69[0-9]|7[0-9][0-9])( |$)/ ~1.7kB\2/g" \
-e "s/ *1(79[0-9]|8[0-9][0-9])( |$)/ ~1.8kB\2/g" \
-e "s/ *2(1[0-9][0-9]|20[0-1])( |$)/ ~2.1kB\2/g" \
-e "s/ *2(3[0-9][0-9]|4[0-1][0-9])( |$)/ ~2.3kB\2/g" \
-e "s/ *2(5[0-9][0-9]|6[0-1][0-9])( |$)/ ~2.5kB\2/g" \
"$@"
}
test_expect_success 'survey forwards to git repo structure' '
git survey --all-refs >survey-out 2>survey-err &&
git repo structure --top=10 >structure-out 2>structure-err &&
test_cmp structure-out survey-out
'
test_expect_success 'git survey (default)' '
git survey --all-refs >out 2>err &&
test_line_count = 0 err &&
test_expect_success 'survey --top is translated' '
git survey --top=3 --all-refs >out &&
git repo structure --top=3 >expected &&
test_cmp expected out
'
test_oid_cache <<-EOF &&
commits_sizes sha1:~1.5kB | ~2.1kB
commits_sizes sha256:~1.8kB | ~2.5kB
trees_sizes sha1:~0.5kB | ~1.7kB
trees_sizes sha256:~0.6kB | ~2.3kB
blobs_sizes sha1:~0.1kB | ~0.1kB
blobs_sizes sha256:~0.1kB | ~0.1kB
tags_sizes sha1:~0.5kB | ~0.5kB
tags_sizes sha256:~0.5kB | ~0.6kB
EOF
tr , " " >expect <<-EOF &&
GIT SURVEY for "$(pwd)"
-----------------------------------------------------
REFERENCES SUMMARY
========================
, Ref Type | Count
-----------------+------
, Branches | 1
Remote refs | 0
Tags (all) | 2
Tags (annotated) | 2
REACHABLE OBJECT SUMMARY
========================
Object Type | Count
------------+------
Tags | 4
Commits | 10
Trees | 10
Blobs | 10
TOTAL OBJECT SIZES BY TYPE
===============================================
Object Type | Count | Disk Size | Inflated Size
------------+-------+-----------+--------------
Commits | 10 | $(test_oid commits_sizes)
Trees | 10 | $(test_oid trees_sizes)
Blobs | 10 | $(test_oid blobs_sizes)
Tags | 4 | $(test_oid tags_sizes)
EOF
approximate_sizes out >out-edited &&
lines=$(wc -l <expect) &&
head -n "$lines" <out-edited >out-trimmed &&
test_cmp expect out-trimmed &&
for type in "DIRECTORIES" "FILES"
do
for metric in "COUNT" "DISK SIZE" "INFLATED SIZE"
do
grep "TOP $type BY $metric" out || return 1
done || return 1
done
test_expect_success 'survey --branches translates to a refs/heads/* filter' '
git survey --branches >out &&
git repo structure --top=10 \
--ref-filter="refs/heads/*" >expected &&
test_cmp expected out
'
test_done