fix(robomp): backfilled partial-clone blobs before worktree add

`SandboxManager.ensure_workspace` calls `fetch_base_ref` (then
`worktree add origin/<ref>`) and `fetch_pr_head` (then
`worktree add --detach FETCH_HEAD`). Both used to issue a plain
`git fetch origin <ref>`. On a `--filter=blob:none` pool the fetch
inherits `remote.origin.partialclonefilter` from the pool config and
brings the commit + tree but no blobs. The next `worktree add` runs
in a non-token subprocess, hits a missing blob, and tries a lazy
promisor fetch — which under `ProxyGitTransport` deployments has no
PAT in the orchestrator container and dies with

    fatal: could not read Username for 'https://github.com'
    fatal: could not fetch <sha> from promisor remote

`git_ops.fetch_ref` and `fetch_pr_head` now pass `--refetch
--no-filter` so the fetch (which already runs through the token-bearing
transport) eagerly materializes every blob reachable from the requested
ref. `--refetch` is required: without it git short-circuits on "we
already have this commit" and the lazy-fetch path stays primed.
`fetch_prune` (the periodic pool refresh) is untouched, so the
partial-clone disk savings are preserved on the steady-state path.
`remote.origin.partialclonefilter` is left intact in the pool config.

Fixes #1818
This commit is contained in:
roboomp
2026-06-04 05:39:46 +00:00
parent f6fca1f5cd
commit a692ffeb31
2 changed files with 227 additions and 5 deletions
+32 -4
View File
@@ -429,8 +429,29 @@ def fetch_prune(repo_dir: Path, *, token: str | None, safe_directory: Path | Non
def fetch_ref(repo_dir: Path, ref: str, *, token: str | None, safe_directory: Path | None = None) -> None:
"""`git fetch origin <ref>` (best-effort: caller decides to swallow)."""
args = ["fetch", "origin", ref]
"""Fetch ``<ref>`` from origin AND materialize every reachable blob locally.
Callers invoke this immediately before a ``git worktree add`` / checkout
that needs the working-tree contents, so the blobs MUST be present after
this returns. ``<repo_dir>`` is typically a ``--filter=blob:none`` partial
clone: a plain ``git fetch origin <ref>`` would inherit
``remote.origin.partialclonefilter`` from the pool, bringing the commit
and tree but no blobs, leaving the subsequent ``worktree add`` to trigger
a lazy promisor fetch — which in proxy-transport deployments has no PAT
in the orchestrator process and dies with::
fatal: could not read Username for 'https://github.com'
fatal: could not fetch <sha> from promisor remote
(oh-my-pi#1818). ``--refetch`` forces a fresh negotiation that ignores
"we already have this commit", and ``--no-filter`` overrides the inherited
filter for this one invocation without touching the on-disk config — so
``fetch_prune`` keeps its cheap blob-skipping semantics on the next pool
refresh. Best-effort: a non-zero exit is logged and swallowed because the
caller still attempts the checkout (and a stale-ref worktree add will
surface a more actionable error than this fetch ever could).
"""
args = ["fetch", "--refetch", "--no-filter", "origin", ref]
proc = _run_git(args, cwd=repo_dir, token=token, safe_directory=safe_directory)
if proc.returncode != 0:
log.debug(
@@ -446,10 +467,17 @@ def fetch_pr_head(
token: str | None,
safe_directory: Path | None = None,
) -> None:
"""Fetch `refs/pull/<n>/head` into FETCH_HEAD for detached PR review worktrees."""
"""Fetch ``refs/pull/<n>/head`` into FETCH_HEAD with all reachable blobs.
Immediately followed by ``git worktree add --detach FETCH_HEAD`` for PR
review checkouts. See :func:`fetch_ref` for why ``--refetch --no-filter``
is required: without the blob backfill, the worktree-add triggers a
promisor lazy fetch that fails under proxy-transport deployments
(oh-my-pi#1818).
"""
if pr_number <= 0:
raise ValueError(f"invalid PR number: {pr_number!r}")
args = ["fetch", "origin", f"pull/{pr_number}/head"]
args = ["fetch", "--refetch", "--no-filter", "origin", f"pull/{pr_number}/head"]
_check(_run_git(args, cwd=repo_dir, token=token, safe_directory=safe_directory), ["git", *args])