[PATCH RFC 5/5] backfill: report total size of missing blobs in --dry-run
- From
Pablo Sabater <pabloosabaterr@gmail.com>
- Date
- Sep 30, 2026, 00:21 UTC
- Message-ID
- <20260930-backfill-dryrun-v1-5-1128f247ee01@gmail.com>
- In-Reply-To
- <20260930-backfill-dryrun-v1-0-1128f247ee01@gmail.com>
The number of missing blobs says little about how much data a backfill will transfer. In --dry-run mode, if the server has the object-info capability enabled, fetch the size of the objects to be fetched and show the total size, if the server doesn't have the capability enabled, fall back to printing only the count.
The reported size is the sum of the uncompressed object sizes, so it is an upper bound: once fetched, the blobs are stored compressed and possibly deltified in a packfile, and usually take less space on disk.
Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com> --- Documentation/git-backfill.adoc | 6 +++- builtin/backfill.c | 70 ++++++++++++++++++++++++++++++++++++++--- t/t5620-backfill.sh | 22 +++++++++++++ 3 files changed, 92 insertions(+), 6 deletions(-)
diff --git a/Documentation/git-backfill.adoc b/Documentation/git-backfill.adoc index 08f19fea17..660e1c17c0 100644 --- a/Documentation/git-backfill.adoc +++ b/Documentation/git-backfill.adoc @@ -72,7 +72,11 @@ OPTIONS `--dry-run`:: Do not download any objects. Instead, print the number of - missing blobs that would be downloaded. + missing blobs that would be downloaded and, if the promisor + remote supports the `object-info` capability, their total + size. This is the sum of the uncompressed sizes of the blobs, + so the space used on disk after a real backfill is usually + smaller. `<revision-range>`:: Backfill only blobs reachable from commits in the specified diff --git a/builtin/backfill.c b/builtin/backfill.c index 6019112966..caf1a64e95 100644 --- a/builtin/backfill.c +++ b/builtin/backfill.c @@ -24,6 +24,9 @@ #include "progress.h" #include "packfile.h" #include "path-walk.h" +#include "transport.h" +#include "remote.h" +#include "fetch-object-info.h" static const char * const builtin_backfill_usage[] = { N_("git backfill [--min-batch-size=<n>] [--[no-]sparse] [--[no-]include-edges] [--dry-run] [<revision-range>]"), @@ -37,7 +40,11 @@ struct backfill_context { int sparse; int include_edges; int dry_run; + int object_info_enabled; + size_t total_batch_size; size_t total_batch_nr; + struct transport *object_info_transport; + struct fetch_object_info_results object_info_results; struct rev_info revs; }; @@ -62,10 +69,47 @@ static void download_batch(struct backfill_context *ctx) static void dry_run_batch(struct backfill_context *ctx) { + struct fetch_object_info_results *results = &ctx->object_info_results; + enum fetch_object_info_status status; + if (!ctx->current_batch.nr) return; ctx->total_batch_nr += ctx->current_batch.nr; + + if (!ctx->object_info_enabled) + goto cleanup; + + if (!ctx->object_info_transport) { + struct promisor_remote *promise = + repo_promisor_remote_find(ctx->repo, NULL); + struct remote *remote = NULL; + + if (!promise || !(remote = remote_get(promise->name))) + die(_("--dry-run requires a promisor remote")); + + ctx->object_info_transport = transport_get(remote, NULL); + + if (!ctx->object_info_transport->smart_options) + die(_("failed to get object info: smart options required")); + } + + results->wants_size = 1; + status = transport_fetch_object_info(ctx->object_info_transport, + &ctx->current_batch, + results); + + if (status == FETCH_OBJECT_INFO_NOT_ENABLED || + !results->sizes) { + ctx->object_info_enabled = 0; + goto cleanup; + } + + for (size_t i = 0; i < results->nr; i++) + ctx->total_batch_size += results->sizes[i]; + +cleanup: + free_fetch_object_info_results(&ctx->object_info_results); oid_array_clear(&ctx->current_batch); } @@ -157,12 +201,25 @@ static int do_backfill(struct backfill_context *ctx) dry_run_batch(ctx); - printf(Q_("After backfill, %" PRIuMAX " blob would be fetched.\n", - "After backfill, %" PRIuMAX " blobs would be fetched.\n", - (unsigned long)ctx->total_batch_nr), - (uintmax_t)ctx->total_batch_nr); + if (ctx->object_info_enabled && ctx->total_batch_nr) { + struct strbuf size = STRBUF_INIT; + + strbuf_humanise_bytes(&size, ctx->total_batch_size); + printf(Q_("After backfill, %" PRIuMAX " blob would be fetched (%s).\n", + "After backfill, %" PRIuMAX " blobs would be fetched (%s).\n", + (unsigned long)ctx->total_batch_nr), + (uintmax_t)ctx->total_batch_nr, size.buf); + strbuf_release(&size); + } else { + printf(Q_("After backfill, %" PRIuMAX " blob would be fetched.\n", + "After backfill, %" PRIuMAX " blobs would be fetched.\n", + (unsigned long)ctx->total_batch_nr), + (uintmax_t)ctx->total_batch_nr); + } end: + if (ctx->object_info_transport) + transport_disconnect(ctx->object_info_transport); path_walk_info_clear(&info); return ret; } @@ -177,6 +234,8 @@ int cmd_backfill(int argc, const char **argv, const char *prefix, struct reposit .sparse = -1, .revs = REV_INFO_INIT, .include_edges = 1, + .object_info_results = FETCH_OBJECT_INFO_RESULTS_INIT, + .object_info_enabled = 1, }; struct option options[] = { OPT_UNSIGNED(0, "min-batch-size", &ctx.min_batch_size, @@ -185,7 +244,8 @@ int cmd_backfill(int argc, const char **argv, const char *prefix, struct reposit N_("Restrict the missing objects to the current sparse-checkout")), OPT_BOOL(0, "include-edges", &ctx.include_edges, N_("Include blobs from boundary commits in the backfill")), - OPT__DRY_RUN(&ctx.dry_run, N_("Preview the number of blobs to be fetched")), + OPT__DRY_RUN(&ctx.dry_run, N_("Preview the number of blobs and their total " + "size to be fetched")), OPT_END(), }; struct repo_config_values *cfg = repo_config_values(the_repository); diff --git a/t/t5620-backfill.sh b/t/t5620-backfill.sh index e76fa6081b..b21feb625c 100755 --- a/t/t5620-backfill.sh +++ b/t/t5620-backfill.sh @@ -167,6 +167,28 @@ test_expect_success '--dry-run with no missing blobs' ' test_grep "0 blobs would be fetched" out ' +test_expect_success '--dry-run reports total size with object-info' ' + test_config -C srv.bare transfer.advertiseobjectinfo true && + test_when_finished rm -rf backfill-dry-run && + git clone --no-checkout --filter=blob:none \ + --single-branch --branch=main \ + "file://$(pwd)/srv.bare" backfill-dry-run && + + git -C backfill-dry-run backfill --dry-run >out && + test_grep "48 blobs would be fetched (.*)" out +' + +test_expect_success '--dry-run reports only the count without object-info' ' + test_config -C srv.bare transfer.advertiseobjectinfo false && + test_when_finished rm -rf backfill-dry-run && + git clone --no-checkout --filter=blob:none \ + --single-branch --branch=main \ + "file://$(pwd)/srv.bare" backfill-dry-run && + + git -C backfill-dry-run backfill --dry-run >out && + test_grep "48 blobs would be fetched\.$" out +' + test_expect_success 'backfill --sparse without sparse-checkout fails' ' git init not-sparse && test_must_fail git -C not-sparse backfill --sparse 2>err &&
-- 2.54.0