diff --git a/Documentation/git-survey.adoc b/Documentation/git-survey.adoc index 349560eaa7..e97656b49d 100644 --- a/Documentation/git-survey.adoc +++ b/Documentation/git-survey.adoc @@ -17,8 +17,8 @@ NOTE: `git survey` is being superseded by `git repo structure`. New deployments and new features should use `git repo structure`; its `--ref-filter=` option subsumes the various `--branches`, `--tags`, and `--remotes` flags here, and `--top=` provides the -same detail tables. A future release will turn `git survey` into a -thin shim over `git repo structure`. See linkgit:git-repo[1]. +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. diff --git a/builtin/survey.c b/builtin/survey.c index 406e76eb09..d3b36b8914 100644 --- a/builtin/survey.c +++ b/builtin/survey.c @@ -1,934 +1,113 @@ #define USE_THE_REPOSITORY_VARIABLE #include "builtin.h" -#include "config.h" -#include "environment.h" -#include "hex.h" -#include "object.h" -#include "odb.h" -#include "object-name.h" -#include "parse-options.h" -#include "path-walk.h" -#include "progress.h" -#include "ref-filter.h" -#include "refs.h" -#include "revision.h" -#include "strbuf.h" -#include "strvec.h" -#include "tag.h" -#include "trace2.h" #include "color.h" +#include "exec-cmd.h" +#include "gettext.h" +#include "parse-options.h" +#include "strvec.h" static const char * const survey_usage[] = { - N_("(EXPERIMENTAL!) git survey "), + N_("(DEPRECATED!) git survey "), NULL, }; -struct survey_refs_wanted { - int want_all_refs; /* special override */ - - int want_branches; - int want_tags; - int want_remotes; - int want_detached; - int want_other; /* see FILTER_REFS_OTHERS -- refs/notes/, refs/stash/ */ -}; - -static struct survey_refs_wanted default_ref_options = { - .want_all_refs = 1, -}; - -struct survey_opts { - int verbose; - int show_progress; - int top_nr; - struct survey_refs_wanted refs; -}; - -struct survey_report_ref_summary { - size_t refs_nr; - size_t branches_nr; - size_t remote_refs_nr; - size_t tags_nr; - size_t tags_annotated_nr; - size_t others_nr; - size_t unknown_nr; -}; - -struct survey_report_object_summary { - size_t commits_nr; - size_t tags_nr; - size_t trees_nr; - size_t blobs_nr; -}; - -/** - * For some category given by 'label', count the number of objects - * that match that label along with the on-disk size and the size - * after decompressing (both with delta bases and zlib). - */ -struct survey_report_object_size_summary { - char *label; - size_t nr; - size_t disk_size; - size_t inflated_size; - size_t num_missing; -}; - -typedef int (*survey_top_cmp)(void *v1, void *v2); - -static int cmp_by_nr(void *v1, void *v2) -{ - struct survey_report_object_size_summary *s1 = v1; - struct survey_report_object_size_summary *s2 = v2; - - if (s1->nr < s2->nr) - return -1; - if (s1->nr > s2->nr) - return 1; - return 0; -} - -static int cmp_by_disk_size(void *v1, void *v2) -{ - struct survey_report_object_size_summary *s1 = v1; - struct survey_report_object_size_summary *s2 = v2; - - if (s1->disk_size < s2->disk_size) - return -1; - if (s1->disk_size > s2->disk_size) - return 1; - return 0; -} - -static int cmp_by_inflated_size(void *v1, void *v2) -{ - struct survey_report_object_size_summary *s1 = v1; - struct survey_report_object_size_summary *s2 = v2; - - if (s1->inflated_size < s2->inflated_size) - return -1; - if (s1->inflated_size > s2->inflated_size) - return 1; - return 0; -} - -/** - * Store a list of "top" categories by some sorting function. When - * inserting a new category, reorder the list and free the one that - * got ejected (if any). - */ -struct survey_report_top_table { - const char *name; - survey_top_cmp cmp_fn; - size_t nr; - size_t alloc; - - /** - * 'data' stores an array of structs and must be cast into - * the proper array type before evaluating an index. - */ - void *data; -}; - -static void init_top_sizes(struct survey_report_top_table *top, - size_t limit, const char *name, - survey_top_cmp cmp) -{ - struct survey_report_object_size_summary *sz_array; - - top->name = name; - top->cmp_fn = cmp; - top->alloc = limit; - top->nr = 0; - - CALLOC_ARRAY(sz_array, limit); - top->data = sz_array; -} - -MAYBE_UNUSED -static void clear_top_sizes(struct survey_report_top_table *top) -{ - struct survey_report_object_size_summary *sz_array = top->data; - - for (size_t i = 0; i < top->nr; i++) - free(sz_array[i].label); - free(sz_array); -} - -static void maybe_insert_into_top_size(struct survey_report_top_table *top, - struct survey_report_object_size_summary *summary) -{ - struct survey_report_object_size_summary *sz_array = top->data; - size_t pos = top->nr; - - /* Compare against list from the bottom. */ - while (pos > 0 && top->cmp_fn(&sz_array[pos - 1], summary) < 0) - pos--; - - /* Not big enough! */ - if (pos >= top->alloc) - return; - - /* We need to shift the data. */ - if (top->nr == top->alloc) - free(sz_array[top->nr - 1].label); - else - top->nr++; - - for (size_t i = top->nr - 1; i > pos; i--) - memcpy(&sz_array[i], &sz_array[i - 1], sizeof(*sz_array)); - - memcpy(&sz_array[pos], summary, sizeof(*summary)); - sz_array[pos].label = xstrdup(summary->label); -} - -/** - * This struct contains all of the information that needs to be printed - * at the end of the exploration of the repository and its references. - */ -struct survey_report { - struct survey_report_ref_summary refs; - struct survey_report_object_summary reachable_objects; - - struct survey_report_object_size_summary *by_type; - - struct survey_report_top_table *top_paths_by_count; - struct survey_report_top_table *top_paths_by_disk; - struct survey_report_top_table *top_paths_by_inflate; -}; - -#define REPORT_TYPE_COMMIT 0 -#define REPORT_TYPE_TREE 1 -#define REPORT_TYPE_BLOB 2 -#define REPORT_TYPE_TAG 3 -#define REPORT_TYPE_COUNT 4 - -struct survey_context { - struct repository *repo; - - /* Options that control what is done. */ - struct survey_opts opts; - - /* Info for output only. */ - struct survey_report report; - - /* - * The rest of the members are about enabling the activity - * of the 'git survey' command, including ref listings, object - * pointers, and progress. - */ - - struct progress *progress; - size_t progress_nr; - size_t progress_total; - - struct strvec refs; - struct ref_array ref_array; -}; - -static void clear_survey_context(struct survey_context *ctx) -{ - ref_array_clear(&ctx->ref_array); - strvec_clear(&ctx->refs); -} - -struct survey_table { - const char *table_name; - struct strvec header; - struct strvec *rows; - size_t rows_nr; - size_t rows_alloc; -}; - -#define SURVEY_TABLE_INIT { \ - .header = STRVEC_INIT, \ -} - -static void clear_table(struct survey_table *table) -{ - strvec_clear(&table->header); - for (size_t i = 0; i < table->rows_nr; i++) - strvec_clear(&table->rows[i]); - free(table->rows); -} - -static void insert_table_rowv(struct survey_table *table, ...) -{ - va_list ap; - char *arg; - ALLOC_GROW(table->rows, table->rows_nr + 1, table->rows_alloc); - - memset(&table->rows[table->rows_nr], 0, sizeof(struct strvec)); - - va_start(ap, table); - while ((arg = va_arg(ap, char *))) - strvec_push(&table->rows[table->rows_nr], arg); - va_end(ap); - - table->rows_nr++; -} - -#define SECTION_SEGMENT "========================================" -#define SECTION_SEGMENT_LEN 40 -static const char *section_line = SECTION_SEGMENT - SECTION_SEGMENT - SECTION_SEGMENT - SECTION_SEGMENT; -static const size_t section_len = 4 * SECTION_SEGMENT_LEN; - -static void print_table_title(const char *name, size_t *widths, size_t nr) -{ - size_t width = 3 * (nr - 1); - size_t min_width = strlen(name); - - for (size_t i = 0; i < nr; i++) - width += widths[i]; - - if (width < min_width) - width = min_width; - - if (width > section_len) - width = section_len; - - printf("\n%s\n%.*s\n", name, (int)width, section_line); -} - -static void print_row_plaintext(struct strvec *row, size_t *widths) -{ - static struct strbuf line = STRBUF_INIT; - strbuf_setlen(&line, 0); - - for (size_t i = 0; i < row->nr; i++) { - const char *str = row->v[i]; - size_t len = strlen(str); - if (i) - strbuf_add(&line, " | ", 3); - strbuf_addchars(&line, ' ', widths[i] - len); - strbuf_add(&line, str, len); - } - printf("%s\n", line.buf); -} - -static void print_divider_plaintext(size_t *widths, size_t nr) -{ - static struct strbuf line = STRBUF_INIT; - strbuf_setlen(&line, 0); - - for (size_t i = 0; i < nr; i++) { - if (i) - strbuf_add(&line, "-+-", 3); - strbuf_addchars(&line, '-', widths[i]); - } - printf("%s\n", line.buf); -} - -static void print_table_plaintext(struct survey_table *table) -{ - size_t *column_widths; - size_t columns_nr = table->header.nr; - CALLOC_ARRAY(column_widths, columns_nr); - - for (size_t i = 0; i < columns_nr; i++) { - column_widths[i] = strlen(table->header.v[i]); - - for (size_t j = 0; j < table->rows_nr; j++) { - size_t rowlen = strlen(table->rows[j].v[i]); - if (column_widths[i] < rowlen) - column_widths[i] = rowlen; - } - } - - print_table_title(table->table_name, column_widths, columns_nr); - print_row_plaintext(&table->header, column_widths); - print_divider_plaintext(column_widths, columns_nr); - - for (size_t j = 0; j < table->rows_nr; j++) - print_row_plaintext(&table->rows[j], column_widths); - - free(column_widths); -} - -static void survey_report_plaintext_refs(struct survey_context *ctx) -{ - struct survey_report_ref_summary *refs = &ctx->report.refs; - struct survey_table table = SURVEY_TABLE_INIT; - - table.table_name = _("REFERENCES SUMMARY"); - - strvec_push(&table.header, _("Ref Type")); - strvec_push(&table.header, _("Count")); - - if (ctx->opts.refs.want_all_refs || ctx->opts.refs.want_branches) { - char *fmt = xstrfmt("%"PRIuMAX"", (uintmax_t)refs->branches_nr); - insert_table_rowv(&table, _("Branches"), fmt, NULL); - free(fmt); - } - - if (ctx->opts.refs.want_all_refs || ctx->opts.refs.want_remotes) { - char *fmt = xstrfmt("%"PRIuMAX"", (uintmax_t)refs->remote_refs_nr); - insert_table_rowv(&table, _("Remote refs"), fmt, NULL); - free(fmt); - } - - if (ctx->opts.refs.want_all_refs || ctx->opts.refs.want_tags) { - char *fmt = xstrfmt("%"PRIuMAX"", (uintmax_t)refs->tags_nr); - insert_table_rowv(&table, _("Tags (all)"), fmt, NULL); - free(fmt); - fmt = xstrfmt("%"PRIuMAX"", (uintmax_t)refs->tags_annotated_nr); - insert_table_rowv(&table, _("Tags (annotated)"), fmt, NULL); - free(fmt); - } - - print_table_plaintext(&table); - clear_table(&table); -} - -static void survey_report_plaintext_reachable_object_summary(struct survey_context *ctx) -{ - struct survey_report_object_summary *objs = &ctx->report.reachable_objects; - struct survey_table table = SURVEY_TABLE_INIT; - char *fmt; - - table.table_name = _("REACHABLE OBJECT SUMMARY"); - - strvec_push(&table.header, _("Object Type")); - strvec_push(&table.header, _("Count")); - - fmt = xstrfmt("%"PRIuMAX"", (uintmax_t)objs->tags_nr); - insert_table_rowv(&table, _("Tags"), fmt, NULL); - free(fmt); - - fmt = xstrfmt("%"PRIuMAX"", (uintmax_t)objs->commits_nr); - insert_table_rowv(&table, _("Commits"), fmt, NULL); - free(fmt); - - fmt = xstrfmt("%"PRIuMAX"", (uintmax_t)objs->trees_nr); - insert_table_rowv(&table, _("Trees"), fmt, NULL); - free(fmt); - - fmt = xstrfmt("%"PRIuMAX"", (uintmax_t)objs->blobs_nr); - insert_table_rowv(&table, _("Blobs"), fmt, NULL); - free(fmt); - - print_table_plaintext(&table); - clear_table(&table); -} - -static void survey_report_object_sizes(const char *title, - const char *categories, - struct survey_report_object_size_summary *summary, - size_t summary_nr) -{ - struct survey_table table = SURVEY_TABLE_INIT; - table.table_name = title; - - strvec_push(&table.header, categories); - strvec_push(&table.header, _("Count")); - strvec_push(&table.header, _("Disk Size")); - strvec_push(&table.header, _("Inflated Size")); - - for (size_t i = 0; i < summary_nr; i++) { - char *label_str = xstrdup(summary[i].label); - char *nr_str = xstrfmt("%"PRIuMAX, (uintmax_t)summary[i].nr); - char *disk_str = xstrfmt("%"PRIuMAX, (uintmax_t)summary[i].disk_size); - char *inflate_str = xstrfmt("%"PRIuMAX, (uintmax_t)summary[i].inflated_size); - - insert_table_rowv(&table, label_str, nr_str, - disk_str, inflate_str, NULL); - - free(label_str); - free(nr_str); - free(disk_str); - free(inflate_str); - } - - print_table_plaintext(&table); - clear_table(&table); -} - -static void survey_report_plaintext_sorted_size( - struct survey_report_top_table *top) -{ - survey_report_object_sizes(top->name, _("Path"), - top->data, top->nr); -} - -static void survey_report_plaintext(struct survey_context *ctx) -{ - printf("GIT SURVEY for \"%s\"\n", ctx->repo->worktree); - printf("-----------------------------------------------------\n"); - survey_report_plaintext_refs(ctx); - survey_report_plaintext_reachable_object_summary(ctx); - survey_report_object_sizes(_("TOTAL OBJECT SIZES BY TYPE"), - _("Object Type"), - ctx->report.by_type, - REPORT_TYPE_COUNT); - - survey_report_plaintext_sorted_size( - &ctx->report.top_paths_by_count[REPORT_TYPE_TREE]); - survey_report_plaintext_sorted_size( - &ctx->report.top_paths_by_count[REPORT_TYPE_BLOB]); - - survey_report_plaintext_sorted_size( - &ctx->report.top_paths_by_disk[REPORT_TYPE_TREE]); - survey_report_plaintext_sorted_size( - &ctx->report.top_paths_by_disk[REPORT_TYPE_BLOB]); - - survey_report_plaintext_sorted_size( - &ctx->report.top_paths_by_inflate[REPORT_TYPE_TREE]); - survey_report_plaintext_sorted_size( - &ctx->report.top_paths_by_inflate[REPORT_TYPE_BLOB]); -} - /* - * After parsing the command line arguments, figure out which refs we - * should scan. - * - * If ANY were given in positive sense, then we ONLY include them and - * do not use the builtin values. + * `git survey` has been superseded by `git repo structure`. To keep + * older callers working while the migration completes, accept the + * `git survey` command line, translate the options into the + * equivalent `git repo structure` invocation, and re-exec. */ -static void fixup_refs_wanted(struct survey_context *ctx) +int cmd_survey(int argc, const char **argv, const char *prefix, + struct repository *repo UNUSED) { - struct survey_refs_wanted *rw = &ctx->opts.refs; - - /* - * `--all-refs` overrides and enables everything. - */ - if (rw->want_all_refs == 1) { - rw->want_branches = 1; - rw->want_tags = 1; - rw->want_remotes = 1; - rw->want_detached = 1; - rw->want_other = 1; - return; - } - - /* - * If none of the `--` were given, we assume all - * of the builtin unspecified values. - */ - if (rw->want_branches == -1 && - rw->want_tags == -1 && - rw->want_remotes == -1 && - rw->want_detached == -1 && - rw->want_other == -1) { - *rw = default_ref_options; - return; - } - - /* - * Since we only allow positive boolean values on the command - * line, we will only have true values where they specified - * a `--`. - * - * So anything that still has an unspecified value should be - * set to false. - */ - if (rw->want_branches == -1) - rw->want_branches = 0; - if (rw->want_tags == -1) - rw->want_tags = 0; - if (rw->want_remotes == -1) - rw->want_remotes = 0; - if (rw->want_detached == -1) - rw->want_detached = 0; - if (rw->want_other == -1) - rw->want_other = 0; -} - -static int survey_load_config_cb(const char *var, const char *value, - const struct config_context *cctx, void *pvoid) -{ - struct survey_context *ctx = pvoid; - - if (!strcmp(var, "survey.verbose")) { - ctx->opts.verbose = git_config_bool(var, value); - return 0; - } - if (!strcmp(var, "survey.progress")) { - ctx->opts.show_progress = git_config_bool(var, value); - return 0; - } - if (!strcmp(var, "survey.top")) { - ctx->opts.top_nr = git_config_bool(var, value); - return 0; - } - - return git_default_config(var, value, cctx, pvoid); -} - -static void survey_load_config(struct survey_context *ctx) -{ - repo_config(the_repository, survey_load_config_cb, ctx); -} - -static void do_load_refs(struct survey_context *ctx, - struct ref_array *ref_array) -{ - struct ref_filter filter = REF_FILTER_INIT; - struct ref_sorting *sorting; - struct string_list sorting_options = STRING_LIST_INIT_DUP; - - string_list_append(&sorting_options, "objectname"); - sorting = ref_sorting_options(&sorting_options); - - if (ctx->opts.refs.want_detached) - strvec_push(&ctx->refs, "HEAD"); - - if (ctx->opts.refs.want_all_refs) { - strvec_push(&ctx->refs, "refs/"); - } else { - if (ctx->opts.refs.want_branches) - strvec_push(&ctx->refs, "refs/heads/"); - if (ctx->opts.refs.want_tags) - strvec_push(&ctx->refs, "refs/tags/"); - if (ctx->opts.refs.want_remotes) - strvec_push(&ctx->refs, "refs/remotes/"); - if (ctx->opts.refs.want_other) { - strvec_push(&ctx->refs, "refs/notes/"); - strvec_push(&ctx->refs, "refs/stash/"); - } - } - - filter.name_patterns = ctx->refs.v; - filter.ignore_case = 0; - filter.match_as_path = 1; - - if (ctx->opts.show_progress) { - ctx->progress_total = 0; - ctx->progress = start_progress(ctx->repo, - _("Scanning refs..."), 0); - } - - filter_refs(ref_array, &filter, FILTER_REFS_KIND_MASK); - - if (ctx->opts.show_progress) { - ctx->progress_total = ref_array->nr; - display_progress(ctx->progress, ctx->progress_total); - } - - ref_array_sort(sorting, ref_array); - - stop_progress(&ctx->progress); - ref_filter_clear(&filter); - ref_sorting_release(sorting); -} - -/* - * The REFS phase: - * - * Load the set of requested refs and assess them for scalablity problems. - * Use that set to start a treewalk to all reachable objects and assess - * them. - * - * This data will give us insights into the repository itself (the number - * of refs, the size and shape of the DAG, the number and size of the - * objects). - * - * Theoretically, this data is independent of the on-disk representation - * (e.g. independent of packing concerns). - */ -static void survey_phase_refs(struct survey_context *ctx) -{ - trace2_region_enter("survey", "phase/refs", ctx->repo); - do_load_refs(ctx, &ctx->ref_array); - - ctx->report.refs.refs_nr = ctx->ref_array.nr; - for (int i = 0; i < ctx->ref_array.nr; i++) { - size_t size; - struct ref_array_item *item = ctx->ref_array.items[i]; - - switch (item->kind) { - case FILTER_REFS_TAGS: - ctx->report.refs.tags_nr++; - if (odb_read_object_info(ctx->repo->objects, - &item->objectname, - &size) == OBJ_TAG) - ctx->report.refs.tags_annotated_nr++; - break; - - case FILTER_REFS_BRANCHES: - ctx->report.refs.branches_nr++; - break; - - case FILTER_REFS_REMOTES: - ctx->report.refs.remote_refs_nr++; - break; - - case FILTER_REFS_OTHERS: - ctx->report.refs.others_nr++; - break; - - default: - ctx->report.refs.unknown_nr++; - break; - } - } - - trace2_region_leave("survey", "phase/refs", ctx->repo); -} - -static void increment_object_counts( - struct survey_report_object_summary *summary, - enum object_type type, - size_t nr) -{ - switch (type) { - case OBJ_COMMIT: - summary->commits_nr += nr; - break; - - case OBJ_TREE: - summary->trees_nr += nr; - break; - - case OBJ_BLOB: - summary->blobs_nr += nr; - break; - - case OBJ_TAG: - summary->tags_nr += nr; - break; - - default: - break; - } -} - -static void increment_totals(struct survey_context *ctx, - struct oid_array *oids, - struct survey_report_object_size_summary *summary) -{ - for (size_t i = 0; i < oids->nr; i++) { - struct object_info oi = OBJECT_INFO_INIT; - unsigned oi_flags = OBJECT_INFO_FOR_PREFETCH; - size_t object_length = 0; - off_t disk_sizep = 0; - enum object_type type; - - oi.typep = &type; - oi.sizep = &object_length; - oi.disk_sizep = &disk_sizep; - - if (odb_read_object_info_extended(ctx->repo->objects, - &oids->oid[i], - &oi, oi_flags) < 0) { - summary->num_missing++; - } else { - summary->nr++; - summary->disk_size += disk_sizep; - summary->inflated_size += object_length; - } - } -} - -static void increment_object_totals(struct survey_context *ctx, - struct oid_array *oids, - enum object_type type, - const char *path) -{ - struct survey_report_object_size_summary *total; - struct survey_report_object_size_summary summary = { 0 }; - - increment_totals(ctx, oids, &summary); - - switch (type) { - case OBJ_COMMIT: - total = &ctx->report.by_type[REPORT_TYPE_COMMIT]; - break; - - case OBJ_TREE: - total = &ctx->report.by_type[REPORT_TYPE_TREE]; - break; - - case OBJ_BLOB: - total = &ctx->report.by_type[REPORT_TYPE_BLOB]; - break; - - case OBJ_TAG: - total = &ctx->report.by_type[REPORT_TYPE_TAG]; - break; - - default: - BUG("No other type allowed"); - } - - total->nr += summary.nr; - total->disk_size += summary.disk_size; - total->inflated_size += summary.inflated_size; - total->num_missing += summary.num_missing; - - if (type == OBJ_TREE || type == OBJ_BLOB) { - int index = type == OBJ_TREE ? - REPORT_TYPE_TREE : REPORT_TYPE_BLOB; - struct survey_report_top_table *top; - - /* - * Temporarily store (const char *) here, but it will - * be duped if inserted and will not be freed. - */ - summary.label = (char *)path; - - top = ctx->report.top_paths_by_count; - maybe_insert_into_top_size(&top[index], &summary); - - top = ctx->report.top_paths_by_disk; - maybe_insert_into_top_size(&top[index], &summary); - - top = ctx->report.top_paths_by_inflate; - maybe_insert_into_top_size(&top[index], &summary); - } -} - -static int survey_objects_path_walk_fn(const char *path, - struct oid_array *oids, - enum object_type type, - void *data) -{ - struct survey_context *ctx = data; - - increment_object_counts(&ctx->report.reachable_objects, - type, oids->nr); - increment_object_totals(ctx, oids, type, path); - - ctx->progress_nr += oids->nr; - display_progress(ctx->progress, ctx->progress_nr); - - return 0; -} - -static void initialize_report(struct survey_context *ctx) -{ - CALLOC_ARRAY(ctx->report.by_type, REPORT_TYPE_COUNT); - ctx->report.by_type[REPORT_TYPE_COMMIT].label = xstrdup(_("Commits")); - ctx->report.by_type[REPORT_TYPE_TREE].label = xstrdup(_("Trees")); - ctx->report.by_type[REPORT_TYPE_BLOB].label = xstrdup(_("Blobs")); - ctx->report.by_type[REPORT_TYPE_TAG].label = xstrdup(_("Tags")); - - CALLOC_ARRAY(ctx->report.top_paths_by_count, REPORT_TYPE_COUNT); - init_top_sizes(&ctx->report.top_paths_by_count[REPORT_TYPE_TREE], - ctx->opts.top_nr, _("TOP DIRECTORIES BY COUNT"), cmp_by_nr); - init_top_sizes(&ctx->report.top_paths_by_count[REPORT_TYPE_BLOB], - ctx->opts.top_nr, _("TOP FILES BY COUNT"), cmp_by_nr); - - CALLOC_ARRAY(ctx->report.top_paths_by_disk, REPORT_TYPE_COUNT); - init_top_sizes(&ctx->report.top_paths_by_disk[REPORT_TYPE_TREE], - ctx->opts.top_nr, _("TOP DIRECTORIES BY DISK SIZE"), cmp_by_disk_size); - init_top_sizes(&ctx->report.top_paths_by_disk[REPORT_TYPE_BLOB], - ctx->opts.top_nr, _("TOP FILES BY DISK SIZE"), cmp_by_disk_size); - - CALLOC_ARRAY(ctx->report.top_paths_by_inflate, REPORT_TYPE_COUNT); - init_top_sizes(&ctx->report.top_paths_by_inflate[REPORT_TYPE_TREE], - ctx->opts.top_nr, _("TOP DIRECTORIES BY INFLATED SIZE"), cmp_by_inflated_size); - init_top_sizes(&ctx->report.top_paths_by_inflate[REPORT_TYPE_BLOB], - ctx->opts.top_nr, _("TOP FILES BY INFLATED SIZE"), cmp_by_inflated_size); -} - -static void survey_phase_objects(struct survey_context *ctx) -{ - struct rev_info revs = REV_INFO_INIT; - struct path_walk_info info = PATH_WALK_INFO_INIT; - unsigned int add_flags = 0; - - trace2_region_enter("survey", "phase/objects", ctx->repo); - - info.revs = &revs; - info.path_fn = survey_objects_path_walk_fn; - info.path_fn_data = ctx; - - initialize_report(ctx); - - repo_init_revisions(ctx->repo, &revs, ""); - revs.tag_objects = 1; - - ctx->progress_nr = 0; - ctx->progress_total = ctx->ref_array.nr; - if (ctx->opts.show_progress) - ctx->progress = start_progress(ctx->repo, - _("Preparing object walk"), - ctx->progress_total); - for (int i = 0; i < ctx->ref_array.nr; i++) { - struct ref_array_item *item = ctx->ref_array.items[i]; - add_pending_oid(&revs, NULL, &item->objectname, add_flags); - display_progress(ctx->progress, ++(ctx->progress_nr)); - } - stop_progress(&ctx->progress); - - ctx->progress_nr = 0; - ctx->progress_total = 0; - if (ctx->opts.show_progress) - ctx->progress = start_progress(ctx->repo, - _("Walking objects"), 0); - walk_objects_by_path(&info); - stop_progress(&ctx->progress); - - release_revisions(&revs); - trace2_region_leave("survey", "phase/objects", ctx->repo); -} - -int cmd_survey(int argc, const char **argv, const char *prefix, struct repository *repo) -{ - static struct survey_context ctx = { - .opts = { - .verbose = 0, - .show_progress = -1, /* defaults to isatty(2) */ - .top_nr = 10, - - .refs.want_all_refs = -1, - - .refs.want_branches = -1, /* default these to undefined */ - .refs.want_tags = -1, - .refs.want_remotes = -1, - .refs.want_detached = -1, - .refs.want_other = -1, - }, - .refs = STRVEC_INIT, - }; - - static struct option survey_options[] = { - OPT__VERBOSE(&ctx.opts.verbose, N_("verbose output")), - OPT_BOOL(0, "progress", &ctx.opts.show_progress, N_("show progress")), - OPT_INTEGER('n', "top", &ctx.opts.top_nr, - N_("number of entries to include in detail tables")), - - OPT_BOOL_F(0, "all-refs", &ctx.opts.refs.want_all_refs, N_("include all refs"), PARSE_OPT_NONEG), - - OPT_BOOL_F(0, "branches", &ctx.opts.refs.want_branches, N_("include branches"), PARSE_OPT_NONEG), - OPT_BOOL_F(0, "tags", &ctx.opts.refs.want_tags, N_("include tags"), PARSE_OPT_NONEG), - OPT_BOOL_F(0, "remotes", &ctx.opts.refs.want_remotes, N_("include all remotes refs"), PARSE_OPT_NONEG), - OPT_BOOL_F(0, "detached", &ctx.opts.refs.want_detached, N_("include detached HEAD"), PARSE_OPT_NONEG), - OPT_BOOL_F(0, "other", &ctx.opts.refs.want_other, N_("include notes and stashes"), PARSE_OPT_NONEG), - + int verbose = 0; + int show_progress = -1; + int top_nr = 10; + int want_all_refs = -1; + int want_branches = -1; + int want_tags = -1; + int want_remotes = -1; + int want_detached = -1; + int want_other = -1; + struct strvec child_argv = STRVEC_INIT; + struct option options[] = { + OPT__VERBOSE(&verbose, N_("verbose output (ignored)")), + OPT_BOOL(0, "progress", &show_progress, N_("show progress")), + OPT_INTEGER('n', "top", &top_nr, + N_("number of entries to include in " + "detail tables")), + OPT_BOOL_F(0, "all-refs", &want_all_refs, + N_("include all refs"), PARSE_OPT_NONEG), + OPT_BOOL_F(0, "branches", &want_branches, + N_("include branches"), PARSE_OPT_NONEG), + OPT_BOOL_F(0, "tags", &want_tags, + N_("include tags"), PARSE_OPT_NONEG), + OPT_BOOL_F(0, "remotes", &want_remotes, + N_("include remote refs"), PARSE_OPT_NONEG), + OPT_BOOL_F(0, "detached", &want_detached, + N_("include detached HEAD (ignored)"), + PARSE_OPT_NONEG), + OPT_BOOL_F(0, "other", &want_other, + N_("include notes and stashes"), PARSE_OPT_NONEG), OPT_END(), }; - show_usage_with_options_if_asked(argc, argv, - survey_usage, survey_options); + argc = parse_options(argc, argv, prefix, options, survey_usage, 0); + if (top_nr < 0) + die(_("--top= must be non-negative")); + if (argc) + usage(_("'git survey' takes no positional arguments")); - if (isatty(2)) - color_fprintf_ln(stderr, - want_color_fd(2, GIT_COLOR_AUTO) ? GIT_COLOR_YELLOW : "", - "(THIS IS EXPERIMENTAL, EXPECT THE OUTPUT FORMAT TO CHANGE!)"); + warning(_("'git survey' is deprecated; " + "use 'git repo structure' instead")); + if (verbose) + warning(_("--verbose is ignored by 'git repo structure'")); + if (want_detached != -1) + warning(_("--detached is ignored by 'git repo structure'")); - ctx.repo = repo; + strvec_pushl(&child_argv, "repo", "structure", NULL); + if (show_progress == 1) + strvec_push(&child_argv, "--progress"); + else if (show_progress == 0) + strvec_push(&child_argv, "--no-progress"); + strvec_pushf(&child_argv, "--top=%d", top_nr); - prepare_repo_settings(ctx.repo); - survey_load_config(&ctx); + /* + * Survey's default ref scope is branches+tags+remotes (not "other"). + * `--all-refs` widens to literally everything; the per-kind flags + * select specific subsets. `git repo structure` defaults to all + * refs and accepts a repeatable --ref-filter=, so the + * translation is straightforward. + */ + if (want_all_refs != 1) { + int branches = want_branches == 1; + int tags = want_tags == 1; + int remotes = want_remotes == 1; + int other = want_other == 1; - argc = parse_options(argc, argv, prefix, survey_options, survey_usage, 0); + if (!branches && !tags && !remotes && !other) + branches = tags = remotes = 1; - if (ctx.opts.show_progress < 0) - ctx.opts.show_progress = isatty(2); + if (branches) + strvec_push(&child_argv, + "--ref-filter=refs/heads/*"); + if (tags) + strvec_push(&child_argv, + "--ref-filter=refs/tags/*"); + if (remotes) + strvec_push(&child_argv, + "--ref-filter=refs/remotes/*"); + if (other) { + strvec_push(&child_argv, + "--ref-filter=refs/notes/*"); + strvec_push(&child_argv, + "--ref-filter=refs/stash"); + } + } - fixup_refs_wanted(&ctx); - - survey_phase_refs(&ctx); - - survey_phase_objects(&ctx); - - survey_report_plaintext(&ctx); - - clear_survey_context(&ctx); - return 0; + execv_git_cmd(child_argv.v); + /* unreachable: execv_git_cmd dies on failure */ + strvec_clear(&child_argv); + return 1; } diff --git a/t/t8100-git-survey.sh b/t/t8100-git-survey.sh index 1ba48cc47e..dec9d81ec6 100755 --- a/t/t8100-git-survey.sh +++ b/t/t8100-git-survey.sh @@ -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 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