builtin/repo: add progress meter for structure stats

When using the structure subcommand for git-repo(1), evaluating a repository may take some time depending on its shape. Add a progress meter to provide feedback to the user about what is happening. The progress meter is enabled by default when the command is executed from a tty. It can also be explicitly enabled/disabled via the --[no-]progress option. Signed-off-by: Justin Tobler <jltobler@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Justin Tobler committed Oct 21, 2025 at 13:26 UTC 16a93c03c7824a40b034a6ee1cb1c68c8ef48682
2 files changed +60 -6
builtin/repo.c
+40 -6
@@ -4,6 +4,7 @@
4 #include "environment.h"
5 #include "parse-options.h"
6 #include "path-walk.h"
7 +#include "progress.h"
8 #include "quote.h"
9 #include "ref-filter.h"
10 #include "refs.h"
@@ -362,6 +363,7 @@ static void structure_keyvalue_print(struct repo_structure *stats,
363 struct count_references_data {
364 struct ref_stats *stats;
365 struct rev_info *revs;
366 + struct progress *progress;
367 };
368
369 static int count_references(const char *refname,
@@ -371,6 +373,7 @@ static int count_references(const char *refname,
373 {
374 struct count_references_data *data = cb_data;
375 struct ref_stats *stats = data->stats;
376 + size_t ref_count;
377
378 switch (ref_kind_from_refname(refname)) {
379 case FILTER_REFS_BRANCHES:
@@ -395,26 +398,41 @@ static int count_references(const char *refname,
398 */
399 add_pending_oid(data->revs, NULL, oid, 0);
400
401 + ref_count = get_total_reference_count(stats);
402 + display_progress(data->progress, ref_count);
403 +
404 return 0;
405 }
406
407 static void structure_count_references(struct ref_stats *stats,
408 struct rev_info *revs,
403 - struct repository *repo)
409 + struct repository *repo,
410 + int show_progress)
411 {
412 struct count_references_data data = {
413 .stats = stats,
414 .revs = revs,
415 };
416
417 + if (show_progress)
418 + data.progress = start_delayed_progress(repo,
419 + _("Counting references"), 0);
420 +
421 refs_for_each_ref(get_main_ref_store(repo), count_references, &data);
422 + stop_progress(&data.progress);
423 }
424
425 +struct count_objects_data {
426 + struct object_stats *stats;
427 + struct progress *progress;
428 +};
429
430 static int count_objects(const char *path UNUSED, struct oid_array *oids,
431 enum object_type type, void *cb_data)
432 {
417 - struct object_stats *stats = cb_data;
433 + struct count_objects_data *data = cb_data;
434 + struct object_stats *stats = data->stats;
435 + size_t object_count;
436
437 switch (type) {
438 case OBJ_TAG:
@@ -433,20 +451,31 @@ static int count_objects(const char *path UNUSED, struct oid_array *oids,
451 BUG("invalid object type");
452 }
453
454 + object_count = get_total_object_count(stats);
455 + display_progress(data->progress, object_count);
456 +
457 return 0;
458 }
459
460 static void structure_count_objects(struct object_stats *stats,
440 - struct rev_info *revs)
461 + struct rev_info *revs,
462 + struct repository *repo, int show_progress)
463 {
464 struct path_walk_info info = PATH_WALK_INFO_INIT;
465 + struct count_objects_data data = {
466 + .stats = stats,
467 + };
468
469 info.revs = revs;
470 info.path_fn = count_objects;
446 - info.path_fn_data = stats;
471 + info.path_fn_data = &data;
472 +
473 + if (show_progress)
474 + data.progress = start_delayed_progress(repo, _("Counting objects"), 0);
475
476 walk_objects_by_path(&info);
477 path_walk_info_clear(&info);
478 + stop_progress(&data.progress);
479 }
480
481 static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
@@ -458,10 +487,12 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
487 enum output_format format = FORMAT_TABLE;
488 struct repo_structure stats = { 0 };
489 struct rev_info revs;
490 + int show_progress = -1;
491 struct option options[] = {
492 OPT_CALLBACK_F(0, "format", &format, N_("format"),
493 N_("output format"),
494 PARSE_OPT_NONEG, parse_format_cb),
495 + OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
496 OPT_END()
497 };
498
@@ -471,8 +502,11 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
502
503 repo_init_revisions(repo, &revs, prefix);
504
474 - structure_count_references(&stats.refs, &revs, repo);
475 - structure_count_objects(&stats.objects, &revs);
505 + if (show_progress < 0)
506 + show_progress = isatty(2);
507 +
508 + structure_count_references(&stats.refs, &revs, repo, show_progress);
509 + structure_count_objects(&stats.objects, &revs, repo, show_progress);
510
511 switch (format) {
512 case FORMAT_TABLE:
t/t1901-repo-structure.sh
+20
@@ -106,4 +106,24 @@ test_expect_success 'keyvalue and nul format' '
106 )
107 '
108
109 +test_expect_success 'progress meter option' '
110 + test_when_finished "rm -rf repo" &&
111 + git init repo &&
112 + (
113 + cd repo &&
114 + test_commit foo &&
115 +
116 + GIT_PROGRESS_DELAY=0 git repo structure --progress >out 2>err &&
117 +
118 + test_file_not_empty out &&
119 + test_grep "Counting references: 2, done." err &&
120 + test_grep "Counting objects: 3, done." err &&
121 +
122 + GIT_PROGRESS_DELAY=0 git repo structure --no-progress >out 2>err &&
123 +
124 + test_file_not_empty out &&
125 + test_line_count = 0 err
126 + )
127 +'
128 +
129 test_done