Skip to content

Commit 607ed38

Browse files
committed
backfill: default to grabbing edge blobs too
Commit 302aff0 (backfill: accept revision arguments, 2026-03-26) added support for accepting revision arguments to backfill. This allows users to do things like git backfill --remotes ^v2.3.0 and then run many commands without triggering on-demand downloads of blobs. However, if they have topics based on v2.3.0, they will likely still trigger on-demand downloads. Consider, for example, the command git log -p v2.3.0..topic This would still trigger on-demand blob loadings after the backfill command above, because the commit(s) with A as a parent will need to diff against the blobs in A. In fact, multiple commands need blobs from the lower boundary of the revision range: * git log -p A..B # After backfill A..B * git replay --onto TARGET A..B # After backfill TARGET^! A..B * git checkout A && git merge B # After backfill A...B Add an extra --[no-]include-edges flag to allow grabbing blobs from edge commits. Since the point of backfill is to prevent on-demand blob loading and these are common commands, default to --include-edges. Signed-off-by: Elijah Newren <newren@gmail.com>
1 parent 173831e commit 607ed38

3 files changed

Lines changed: 119 additions & 8 deletions

File tree

Documentation/git-backfill.adoc

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ git-backfill - Download missing objects in a partial clone
99
SYNOPSIS
1010
--------
1111
[synopsis]
12-
git backfill [--min-batch-size=<n>] [--[no-]sparse] [<revision-range>]
12+
git backfill [--min-batch-size=<n>] [--[no-]sparse] [--[no-]include-edges] [<revision-range>]
1313

1414
DESCRIPTION
1515
-----------
@@ -63,6 +63,13 @@ OPTIONS
6363
current sparse-checkout. If the sparse-checkout feature is enabled,
6464
then `--sparse` is assumed and can be disabled with `--no-sparse`.
6565

66+
`--include-edges`::
67+
`--no-include-edges`::
68+
Include blobs from boundary commits in the backfill. Useful in
69+
preparation for commands like `git log -p A..B` or `git replay
70+
--onto TARGET A..B`, where A..B normally excludes A but you need
71+
the blobs from A as well. `--include-edges` is the default.
72+
6673
`<revision-range>`::
6774
Backfill only blobs reachable from commits in the specified
6875
revision range. When no _<revision-range>_ is specified, it

builtin/backfill.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
#include "path-walk.h"
2727

2828
static const char * const builtin_backfill_usage[] = {
29-
N_("git backfill [--min-batch-size=<n>] [--[no-]sparse] [<revision-range>]"),
29+
N_("git backfill [--min-batch-size=<n>] [--[no-]sparse] [--[no-]include-edges] [<revision-range>]"),
3030
NULL
3131
};
3232

@@ -35,6 +35,7 @@ struct backfill_context {
3535
struct oid_array current_batch;
3636
size_t min_batch_size;
3737
int sparse;
38+
int include_edges;
3839
struct rev_info revs;
3940
};
4041

@@ -116,6 +117,8 @@ static int do_backfill(struct backfill_context *ctx)
116117
/* Walk from HEAD if otherwise unspecified. */
117118
if (!ctx->revs.pending.nr)
118119
add_head_to_pending(&ctx->revs);
120+
if (ctx->include_edges)
121+
ctx->revs.edge_hint = 1;
119122

120123
info.blobs = 1;
121124
info.tags = info.commits = info.trees = 0;
@@ -143,12 +146,15 @@ int cmd_backfill(int argc, const char **argv, const char *prefix, struct reposit
143146
.min_batch_size = 50000,
144147
.sparse = -1,
145148
.revs = REV_INFO_INIT,
149+
.include_edges = 1,
146150
};
147151
struct option options[] = {
148152
OPT_UNSIGNED(0, "min-batch-size", &ctx.min_batch_size,
149153
N_("Minimum number of objects to request at a time")),
150154
OPT_BOOL(0, "sparse", &ctx.sparse,
151155
N_("Restrict the missing objects to the current sparse-checkout")),
156+
OPT_BOOL(0, "include-edges", &ctx.include_edges,
157+
N_("Include blobs from boundary commits in the backfill")),
152158
OPT_END(),
153159
};
154160
struct repo_config_values *cfg = repo_config_values(the_repository);

t/t5620-backfill.sh

Lines changed: 104 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -257,11 +257,12 @@ test_expect_success 'backfill with revision range' '
257257
git -C backfill-revs rev-list --quiet --objects --missing=print HEAD >missing &&
258258
test_line_count = 48 missing &&
259259
260-
git -C backfill-revs backfill HEAD~2..HEAD &&
260+
GIT_TRACE2_EVENT="$(pwd)/backfill-trace" git -C backfill-revs backfill HEAD~2..HEAD &&
261261
262-
# 30 objects downloaded.
262+
# 36 objects downloaded, 12 still missing
263+
test_trace2_data promisor fetch_count 36 <backfill-trace &&
263264
git -C backfill-revs rev-list --quiet --objects --missing=print HEAD >missing &&
264-
test_line_count = 18 missing
265+
test_line_count = 12 missing
265266
'
266267

267268
test_expect_success 'backfill with revisions over stdin' '
@@ -279,11 +280,12 @@ test_expect_success 'backfill with revisions over stdin' '
279280
^HEAD~2
280281
EOF
281282
282-
git -C backfill-revs backfill --stdin <in &&
283+
GIT_TRACE2_EVENT="$(pwd)/backfill-trace" git -C backfill-revs backfill --stdin <in &&
283284
284-
# 30 objects downloaded.
285+
# 36 objects downloaded, 12 still missing
286+
test_trace2_data promisor fetch_count 36 <backfill-trace &&
285287
git -C backfill-revs rev-list --quiet --objects --missing=print HEAD >missing &&
286-
test_line_count = 18 missing
288+
test_line_count = 12 missing
287289
'
288290

289291
test_expect_success 'backfill with prefix pathspec' '
@@ -398,6 +400,102 @@ test_expect_success 'backfill with --since' '
398400
test_line_count = 6 missing
399401
'
400402

403+
test_expect_success 'backfill range with include-edges enables fetch-free git-log' '
404+
git clone --no-checkout --filter=blob:none \
405+
--single-branch --branch=main \
406+
"file://$(pwd)/srv.bare" backfill-log &&
407+
408+
# Backfill the range with default include edges.
409+
git -C backfill-log backfill HEAD~2..HEAD &&
410+
411+
# git log -p needs edge blobs for the "before" side of
412+
# diffs. With edge inclusion, all needed blobs are local.
413+
GIT_TRACE2_EVENT="$(pwd)/log-trace" git \
414+
-C backfill-log log -p HEAD~2..HEAD >log-output &&
415+
416+
# No promisor fetches should have been needed.
417+
! grep "fetch_count" log-trace
418+
'
419+
420+
test_expect_success 'backfill range without include edges causes on-demand fetches in git-log' '
421+
git clone --no-checkout --filter=blob:none \
422+
--single-branch --branch=main \
423+
"file://$(pwd)/srv.bare" backfill-log-no-bdy &&
424+
425+
# Backfill WITHOUT include edges -- file.3 v1 blobs are missing.
426+
git -C backfill-log-no-bdy backfill --no-include-edges HEAD~2..HEAD &&
427+
428+
# git log -p HEAD~2..HEAD computes diff of commit 7 against
429+
# commit 6. It needs file.3 v1 (the "before" side), which was
430+
# not backfilled. This triggers on-demand promisor fetches.
431+
GIT_TRACE2_EVENT="$(pwd)/log-no-bdy-trace" git \
432+
-C backfill-log-no-bdy log -p HEAD~2..HEAD >log-output &&
433+
434+
grep "fetch_count" log-no-bdy-trace
435+
'
436+
437+
test_expect_success 'backfill range enables fetch-free replay' '
438+
# Create a repo with a branch to replay.
439+
git init replay-src &&
440+
(
441+
cd replay-src &&
442+
git config uploadpack.allowfilter 1 &&
443+
git config uploadpack.allowanysha1inwant 1 &&
444+
test_commit base &&
445+
git checkout -b topic &&
446+
test_commit topic-change &&
447+
git checkout main &&
448+
test_commit main-change
449+
) &&
450+
git clone --bare --filter=blob:none \
451+
"file://$(pwd)/replay-src" replay-dest.git &&
452+
453+
# Backfill the replay range: --onto main, replaying topic~1..topic.
454+
# For replay, we need TARGET^! plus the range.
455+
main_oid=$(git -C replay-dest.git rev-parse main) &&
456+
topic_oid=$(git -C replay-dest.git rev-parse topic) &&
457+
base_oid=$(git -C replay-dest.git rev-parse topic~1) &&
458+
git -C replay-dest.git backfill \
459+
"$main_oid^!" "$base_oid..$topic_oid" &&
460+
461+
# Now replay should complete without any promisor fetches.
462+
GIT_TRACE2_EVENT="$(pwd)/replay-trace" git -C replay-dest.git \
463+
replay --onto main topic~1..topic >replay-out &&
464+
465+
! grep "fetch_count" replay-trace
466+
'
467+
468+
test_expect_success 'backfill enables fetch-free merge' '
469+
# Create a repo with two branches to merge.
470+
git init merge-src &&
471+
(
472+
cd merge-src &&
473+
git config uploadpack.allowfilter 1 &&
474+
git config uploadpack.allowanysha1inwant 1 &&
475+
test_commit merge-base &&
476+
git checkout -b side &&
477+
test_commit side-change &&
478+
git checkout main &&
479+
test_commit main-side-change
480+
) &&
481+
git clone --filter=blob:none \
482+
"file://$(pwd)/merge-src" merge-dest &&
483+
484+
# The clone checked out main, fetching its blobs.
485+
# Backfill the three endpoint commits needed for merge.
486+
main_oid=$(git -C merge-dest rev-parse origin/main) &&
487+
side_oid=$(git -C merge-dest rev-parse origin/side) &&
488+
mbase=$(git -C merge-dest merge-base origin/main origin/side) &&
489+
git -C merge-dest backfill --no-include-edges \
490+
"$main_oid^!" "$side_oid^!" "$mbase^!" &&
491+
492+
# Merge should complete without promisor fetches.
493+
GIT_TRACE2_EVENT="$(pwd)/merge-trace" git -C merge-dest \
494+
merge origin/side -m "test merge" &&
495+
496+
! grep "fetch_count" merge-trace
497+
'
498+
401499
. "$TEST_DIRECTORY"/lib-httpd.sh
402500
start_httpd
403501

0 commit comments

Comments
 (0)