Skip to content

Commit 14d1696

Browse files
committed
promisor-remote: prevent lazy-fetch recursion in child fetch
fetch_objects() spawns a child `git fetch` to lazily fill in missing objects. That child's index-pack, when it receives a thin pack containing a REF_DELTA against a still-missing base, explicitly calls promisor_remote_get_direct() — which is fetch_objects() again. If the base is truly unavailable (e.g. because many refs in the local store point at objects that have been garbage-collected on the server), each recursive lazy-fetch can trigger another, leading to unbounded recursion with runaway disk and process consumption. The GIT_NO_LAZY_FETCH guard (introduced by e6d5479 (git: add --no-lazy-fetch option, 2021-08-31)) already exists at the top of fetch_objects(); the missing piece is propagating it into the child fetch's environment. Add that propagation so the child's index-pack, if it encounters a REF_DELTA against a missing base, hits the guard and fails fast instead of recursing. Depth-1 lazy fetch (the whole point of fetch_objects()) is unaffected: only the child and its descendants see the variable. With negotiationAlgorithm=noop the client advertises no "have" lines, so a well-behaved server sends requested objects un-deltified or deltified only against objects in the same pack; the child's index-pack should never need a depth-2 fetch. If it does, the server response was broken or the local store is already corrupt, and further fetching would not help. This is the same bug shape that 3a1ea94 (commit-graph.c: no lazy fetch in lookup_commit_in_graph(), 2022-07-01) addressed at a different entry point. Add a test that verifies the child fetch environment contains GIT_NO_LAZY_FETCH=1 via a reference-transaction hook, and that only one fetch subprocess is spawned. Cc: Jonathan Tan <jonathantanmy@google.com> Cc: Han Xin <hanxin.hx@bytedance.com> Cc: Jeff Hostetler <jeffhostetler@github.com> Cc: Christian Couder <christian.couder@gmail.com> Signed-off-by: Paul Tarjan <github@paulisageek.com>
1 parent 7b2bccb commit 14d1696

File tree

2 files changed

+56
-0
lines changed

2 files changed

+56
-0
lines changed

promisor-remote.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ static int fetch_objects(struct repository *repo,
4242
child.in = -1;
4343
if (repo != the_repository)
4444
prepare_other_repo_env(&child.env, repo->gitdir);
45+
/*
46+
* Prevent the child's index-pack from recursing back into
47+
* fetch_objects() when resolving REF_DELTA bases it does not
48+
* have. With noop negotiation the server should never need
49+
* to send such deltas, so a depth-2 fetch would not help.
50+
*/
51+
strvec_pushf(&child.env, "%s=1", NO_LAZY_FETCH_ENVIRONMENT);
4552
strvec_pushl(&child.args, "-c", "fetch.negotiationAlgorithm=noop",
4653
"fetch", remote_name, "--no-tags",
4754
"--no-write-fetch-head", "--recurse-submodules=no",
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#!/bin/sh
2+
3+
test_description='promisor-remote: no recursive lazy-fetch
4+
5+
Verify that fetch_objects() sets GIT_NO_LAZY_FETCH=1 in the child
6+
fetch environment, so that index-pack cannot recurse back into
7+
fetch_objects() when resolving REF_DELTA bases.
8+
'
9+
10+
. ./test-lib.sh
11+
12+
test_expect_success 'setup' '
13+
test_create_repo server &&
14+
test_commit -C server foo &&
15+
git -C server repack -a -d --write-bitmap-index &&
16+
17+
git clone "file://$(pwd)/server" client &&
18+
HASH=$(git -C client rev-parse foo) &&
19+
rm -rf client/.git/objects/* &&
20+
21+
git -C client config core.repositoryformatversion 1 &&
22+
git -C client config extensions.partialclone "origin"
23+
'
24+
25+
test_expect_success 'lazy-fetch spawns only one fetch subprocess' '
26+
GIT_TRACE="$(pwd)/trace" git -C client cat-file -p "$HASH" &&
27+
28+
grep "git fetch" trace >fetches &&
29+
test_line_count = 1 fetches
30+
'
31+
32+
test_expect_success 'child of lazy-fetch has GIT_NO_LAZY_FETCH=1' '
33+
rm -rf client/.git/objects/* &&
34+
35+
# Install a reference-transaction hook to record the env var
36+
# as seen by processes inside the child fetch.
37+
test_hook -C client reference-transaction <<-\EOF &&
38+
echo "$GIT_NO_LAZY_FETCH" >>../env-in-child
39+
EOF
40+
41+
rm -f env-in-child &&
42+
git -C client cat-file -p "$HASH" &&
43+
44+
# The hook runs inside the child fetch, which should have
45+
# GIT_NO_LAZY_FETCH=1 in its environment.
46+
grep "^1$" env-in-child
47+
'
48+
49+
test_done

0 commit comments

Comments
 (0)