Merge unseen-fetch-bulk: full-objects clone, no per-blob lazy fetch

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Marius Ciepluch
2026-10-10 15:53:05 +02:00
co-authored by Claude Opus 4.8
3 changed files with 22 additions and 10 deletions
+8 -5
View File
@@ -101,11 +101,14 @@ passing implementation tests does not assert that 300 eligible repositories exis
Clones and checkpoints default to `results_root()/unseen-draft` (normally Clones and checkpoints default to `results_root()/unseen-draft` (normally
`~/ousast-results/plane/unseen-draft`), overridable by `--cache /outside/path`. `~/ousast-results/plane/unseen-draft`), overridable by `--cache /outside/path`.
The cache cannot lie inside `--root`. One opaque, disposable clone exists at a time; The cache cannot lie inside `--root`. One opaque, disposable clone exists at a time;
it is removed on success, rejection, or failure. `--filter=blob:none --no-checkout` it is removed on success, rejection, or failure. A full-objects `--no-checkout` clone
keeps the commit graph needed for temporal thirds, blame, and release-tag ancestry, brings the whole commit graph and every blob in one pack, so temporal thirds, blame,
and fetches source blobs on demand. This is a **partial clone**, not a shallow history. and release-tag ancestry read locally; `--no-checkout` still skips the working tree.
The 500 MiB disk cap is checked after Git commands, including lazy blob fetches; This is a full clone, not a shallow history, and has no blob:none promisor, so a
one in-flight command can transiently exceed it before rejection and cleanup. genuinely missing object errors loudly instead of silently round-tripping (the old
promisor path fetched blobs one at a time, hundreds of round-trips per repo). The
500 MiB disk cap is checked after Git commands; one in-flight command can transiently
exceed it before rejection and cleanup.
Git output is captured, never forwarded. Each repository must read a nonempty source Git output is captured, never forwarded. Each repository must read a nonempty source
blob before extraction. Summary instrumentation includes source read operations and blob before extraction. Summary instrumentation includes source read operations and
actual bytes, clone count, cumulative/peak disk bytes, wall time, and logical API calls actual bytes, clone count, cumulative/peak disk bytes, wall time, and logical API calls
+9 -4
View File
@@ -166,9 +166,14 @@ class Clones:
git = Git(path, runner=self.runner, limit_bytes=self.limit_bytes, deadline_seconds=self.deadline_seconds) git = Git(path, runner=self.runner, limit_bytes=self.limit_bytes, deadline_seconds=self.deadline_seconds)
try: try:
# Full commit graph is needed for temporal thirds and OSV brackets. # Full commit graph is needed for temporal thirds and OSV brackets.
# Content reads fetch blobs on demand through Git's promisor mechanism. # A full-objects clone (no blob:none promisor) brings every blob in
# one pack, so blame/log/diff read locally instead of fetching each
# blob on demand (the old promisor path cost ~hundreds of round-trips
# per repo). --no-checkout still skips the working tree. With no
# promisor remote, a genuinely missing object errors loudly rather
# than silently round-tripping. Disk is bounded by limit_bytes below.
done = self.runner( done = self.runner(
["git", "-c", "credential.helper=", "clone", "--quiet", "--filter=blob:none", "--no-checkout", url, str(path)], ["git", "-c", "credential.helper=", "clone", "--quiet", "--no-checkout", url, str(path)],
stdin=subprocess.DEVNULL, stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, stderr=subprocess.PIPE,
@@ -229,7 +234,7 @@ def repository_changes(git, candidate, fixes: set[str], *, seed: int):
# Extra advisory fixes are best-effort exclusions. The candidate # Extra advisory fixes are best-effort exclusions. The candidate
# was fetched strictly by extract_repository. Do not import extra # was fetched strictly by extract_repository. Do not import extra
# tags before introducing() checks the candidate's version bracket. # tags before introducing() checks the candidate's version bracket.
git.run("fetch", "--quiet", "--no-tags", "--filter=blob:none", "origin", fix) git.run("fetch", "--quiet", "--no-tags", "origin", fix)
resolved.append(git.run("rev-parse", fix).strip()) resolved.append(git.run("rev-parse", fix).strip())
except InstrumentFailure as exc: except InstrumentFailure as exc:
if fix == candidate.fix or not str(exc).startswith("git_exit_"): if fix == candidate.fix or not str(exc).startswith("git_exit_"):
@@ -325,7 +330,7 @@ def extract_repository(candidate, github, clones, fixes, seed):
try: try:
with extract.process_deadline(total): with extract.process_deadline(total):
with clones.open(candidate.url) as git: with clones.open(candidate.url) as git:
git.run("fetch", "--quiet", "--filter=blob:none", "origin", candidate.fix) git.run("fetch", "--quiet", "origin", candidate.fix)
changes, exclusions, proof = repository_changes(git, candidate, fixes, seed=seed) changes, exclusions, proof = repository_changes(git, candidate, fixes, seed=seed)
entry = {**asdict(candidate), "changes": changes, "instrument": proof} entry = {**asdict(candidate), "changes": changes, "instrument": proof}
result = {"entry": entry, "ordinary_exclusions": exclusions} result = {"entry": entry, "ordinary_exclusions": exclusions}
+5 -1
View File
@@ -100,7 +100,11 @@ def test_cache_is_outside_repository_deleted_and_partial(tmp_path):
assert git.path.exists() assert git.path.exists()
clone_path = git.path clone_path = git.path
assert not clone_path.exists() assert not clone_path.exists()
assert "--filter=blob:none" in calls[0] and "--no-checkout" in calls[0] # Full-objects clone: no blob:none promisor (which caused per-blob lazy
# fetches), so every blob arrives in one pack and a missing object errors
# loudly. --no-checkout still skips the working tree.
assert "--filter=blob:none" not in calls[0] and "--no-checkout" in calls[0]
assert "clone" in calls[0]
assert clones.counts["clones"] == 1 and clones.counts["clone_bytes"] == 3 assert clones.counts["clones"] == 1 and clones.counts["clone_bytes"] == 3