Merge the promisor-style bulk blob fetch
GitHub will not answer a bare blob id through plain fetch --stdin in a blob:none clone; Git's own lazy fetch shape does. Proven against GameCult/CultLib on Yggdrasil for partial and full clones. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
583b2a7796
1 changed files with 92 additions and 0 deletions
|
|
@ -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<bool> {
|
||||
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(())
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue