From 7f776ec3a6fe1c384e304f539037de1d187faa5c Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 29 Sep 2026 21:06:03 +0200 Subject: [PATCH 1/3] Pin bulk_fetch_objects against a real blob:none partial clone Every existing source-driver fixture uses an origin without uploadpack.allowFilter, so --filter=blob:none is ignored, the clone is full, and bulk_fetch_objects never has a missing blob to fetch. This fixture advertises the filter, so the checkout really lacks the blob, and checks delivery with GIT_NO_LAZY_FETCH=1 so only the bulk fetch can supply it. It fails on the current fetch arguments. Co-Authored-By: Claude Opus 5.5 --- src/drivers.rs | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/drivers.rs b/src/drivers.rs index fbf9558..83a76c6 100644 --- a/src/drivers.rs +++ b/src/drivers.rs @@ -10557,6 +10557,72 @@ Content-Le".to_vec()), |_| {}), Ok(()) } + /// A real partial clone: the origin advertises `uploadpack.allowFilter`, + /// so `--filter=blob:none` is honoured and the checkout holds no blobs. + /// Every other fixture's origin ignores the filter and clones in full, + /// so `bulk_fetch_objects` never had a missing blob to fetch and never + /// met Git's post-fetch connectivity check on a bare blob id. The blob + /// is checked with `GIT_NO_LAZY_FETCH=1`, so only the bulk fetch itself + /// can have delivered it. + #[cfg(unix)] + #[test] + fn bulk_fetch_objects_delivers_a_blob_into_a_blob_none_partial_clone() -> Result<()> { + let temp = tempfile::tempdir()?; + let origin_repo = temp.path().join("origin"); + fs::create_dir(&origin_repo)?; + git_at(&origin_repo, &["init", "--initial-branch=main"])?; + git_at(&origin_repo, &["config", "user.name", "Idunn Test"])?; + git_at( + &origin_repo, + &["config", "user.email", "idunn-test@example.invalid"], + )?; + git_at(&origin_repo, &["config", "uploadpack.allowFilter", "true"])?; + git_at(&origin_repo, &["config", "uploadpack.allowAnySHA1InWant", "true"])?; + fs::write(origin_repo.join("payload.txt"), b"partial clone payload\n")?; + git_at(&origin_repo, &["add", "--all"])?; + git_at(&origin_repo, &["commit", "-m", "fixture"])?; + let blob = git_at(&origin_repo, &["rev-parse", "HEAD:payload.txt"])?; + + let source_cache_root = temp.path().join("source-cache"); + fs::create_dir(&source_cache_root)?; + let driver = GitSourceDriver::new( + source_cache_root.clone(), + temp.path().join("frozen"), + None, + ); + let checkout = source_cache_root.join("checkout"); + driver.git([ + OsString::from("clone"), + OsString::from("--filter=blob:none"), + OsString::from("--no-checkout"), + OsString::from(format!("file://{}", origin_repo.display())), + checkout.as_os_str().to_owned(), + ])?; + + let blob_present = || -> Result { + Ok(Command::new("/usr/bin/git") + .arg("-C") + .arg(&checkout) + .args(["cat-file", "-t", &blob]) + .env_clear() + .env("PATH", "/usr/bin:/bin") + .env("GIT_NO_LAZY_FETCH", "1") + .output()? + .status + .success()) + }; + ensure!( + !blob_present()?, + "fixture is not a partial clone: the blob is already local" + ); + driver.bulk_fetch_objects(&checkout, std::slice::from_ref(&blob))?; + assert!( + blob_present()?, + "bulk fetch succeeded but did not deliver the blob" + ); + Ok(()) + } + /// F2/F3 (Self's rulings, second Cut 1 fix batch): a tree with two /// entries literally named `a` at the same level -- one a symlink /// pointing outside the frozen root, one a subtree holding `pwn` -- must From f9cebb33d0ce12514c31ff7b0eb45819d66937e2 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 29 Sep 2026 21:12:06 +0200 Subject: [PATCH 2/3] Pin the partial-clone bulk fetch arguments in the spy test A file:// origin delivers an explicitly wanted blob with or without the promisor-style flags, so the real-shape fixture cannot fail on the bug that GitHub triggers. The spy test already records the bulk fetch line; assert it carries fetch.negotiationAlgorithm=noop, --recurse-submodules=no and --filter=blob:none. Fails on the current arguments. Co-Authored-By: Claude Opus 5.5 --- src/drivers.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/drivers.rs b/src/drivers.rs index 83a76c6..c39f0bd 100644 --- a/src/drivers.rs +++ b/src/drivers.rs @@ -10554,6 +10554,24 @@ Content-Le".to_vec()), |_| {}), bulk_fetches, 1, "expected exactly one bulk --stdin fetch; invocations:\n{log_text}" ); + // Git's own lazy fetch for a promisor remote sends exactly these; a + // hosted origin (GitHub) omits an explicitly wanted blob, and Git's + // connectivity check then fails, without them. A file:// origin + // delivers the blob either way, so only the arguments can pin this. + let bulk_line = log_text + .lines() + .find(|line| line.contains("fetch") && line.contains("--stdin")) + .unwrap_or_default(); + for required in [ + "fetch.negotiationAlgorithm=noop", + "--recurse-submodules=no", + "--filter=blob:none", + ] { + assert!( + bulk_line.contains(required), + "bulk fetch lacks {required}: {bulk_line}" + ); + } Ok(()) } From 08d94ee61be50cab10b70a8590b3fa8308c42141 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 29 Sep 2026 21:13:22 +0200 Subject: [PATCH 3/3] Fetch blobs in bulk the way Git's lazy fetch does for a promisor remote In a blob:none partial clone, fetching bare blob ids with --stdin made Git's post-fetch connectivity check treat the id as a revision and fail with "bad revision" / "did not send all necessary objects" against GitHub, which broke a live deploy fetching a vendor gitlink's blob. Add fetch.negotiationAlgorithm=noop, --recurse-submodules=no and --filter=blob:none, the arguments Git's own lazy fetch uses. Co-Authored-By: Claude Opus 5.5 --- src/drivers.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/drivers.rs b/src/drivers.rs index c39f0bd..6af3559 100644 --- a/src/drivers.rs +++ b/src/drivers.rs @@ -1133,6 +1133,10 @@ impl GitSourceDriver { /// in depth: Git itself refuses a fetched tree with duplicate names /// before any of it reaches disk, independent of the explicit `fsck` /// call S1 added around the revision this bulk fetch serves. + /// The noop negotiation, `--recurse-submodules=no` and explicit + /// `--filter=blob:none` are the arguments Git's own lazy fetch uses for a + /// promisor remote; without them a hosted origin omits the wanted blob and + /// Git's connectivity check fails on the bare blob id. fn bulk_fetch_objects(&self, repository: &Path, objects: &[String]) -> Result<()> { if objects.is_empty() { return Ok(()); @@ -1155,11 +1159,15 @@ impl GitSourceDriver { let mut command = self.git_command([ OsString::from("-c"), OsString::from("transfer.fsckObjects=true"), + OsString::from("-c"), + OsString::from("fetch.negotiationAlgorithm=noop"), OsString::from("-C"), repository.as_os_str().to_owned(), OsString::from("fetch"), OsString::from("--no-tags"), OsString::from("--no-write-fetch-head"), + OsString::from("--recurse-submodules=no"), + OsString::from("--filter=blob:none"), OsString::from("--stdin"), OsString::from("origin"), ])?;