diff --git a/src/drivers.rs b/src/drivers.rs index fbf9558..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"), ])?; @@ -10554,6 +10562,90 @@ 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(()) + } + + /// 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(()) }