feat(forge): GitHub adapter — second implementation keeps the seam a contract (#2693, milestone 288 step 8)
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / integration (push) Successful in 27s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Successful in 1m8s
CI & Build / Build & push image (push) Successful in 45s
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / integration (push) Successful in 27s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Successful in 1m8s
CI & Build / Build & push image (push) Successful in 45s
ForgeAdapter is now a named base class carrying the shared plumbing (host join, error taxonomy, contents decoding, archive, default_branch, latest_commit); GiteaForge keeps its exact behavior and GitHubForge joins with the real differences: api.github.com / GHE /api/v3 host mapping, Bearer auth, a commits call for the provenance stamp (GitHub's contents payload only carries the blob sha), and the codeload tarball redirect. The contract grew latest_commit, and with it the cached-SHA short-circuit in pull-time freshness: a stored provenance commit that still heads the recorded path confirms 'current' without a content transfer — the economy that fits pulls inside GitHub's rate limits; every surprise falls back to the full fetch. Webhook deliveries now also accept X-Hub-Signature-256 (sha256=<hex>); the payload shape was already common. Settings card copy covers both forges' token scopes; the kind selector already flowed from the server. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1046,10 +1046,27 @@ async def attach_live_body(note, data: dict) -> None:
|
||||
data["body_freshness"] = "repo-not-on-this-forge"
|
||||
return
|
||||
|
||||
stored_prov_sha = (fields.get("provenance") or {}).get("commit_sha") or ""
|
||||
|
||||
async def _probe():
|
||||
# Cached-SHA short-circuit (#2693): provenance names the commit the
|
||||
# cached code was last confirmed at, so one cheap "newest commit
|
||||
# touching this path" call can prove the file hasn't moved since —
|
||||
# no content transfer. That economy is what fits pull-time freshness
|
||||
# inside GitHub's rate limits; it's merely nice on a self-hosted
|
||||
# Gitea. Any surprise (error, empty, mismatch) falls through to the
|
||||
# full fetch, which stays the authoritative path.
|
||||
if stored_prov_sha:
|
||||
try:
|
||||
head = await forge.latest_commit(repo, loc["path"])
|
||||
except ForgeError:
|
||||
head = ""
|
||||
if head and head == stored_prov_sha:
|
||||
return None
|
||||
return await forge.read_file(repo, loc["path"])
|
||||
|
||||
try:
|
||||
fetched = await asyncio.wait_for(
|
||||
forge.read_file(repo, loc["path"]), timeout=PULL_FETCH_BUDGET_S
|
||||
)
|
||||
fetched = await asyncio.wait_for(_probe(), timeout=PULL_FETCH_BUDGET_S)
|
||||
except ForgeNotFound:
|
||||
data["body_source"] = "cache"
|
||||
data["body_freshness"] = "missing"
|
||||
@@ -1064,6 +1081,14 @@ async def attach_live_body(note, data: dict) -> None:
|
||||
data["body_freshness"] = "unreachable"
|
||||
return
|
||||
|
||||
if fetched is None:
|
||||
# Unchanged since the provenance commit — confirmed against the
|
||||
# source without moving the file. Same stamp, so nothing to persist
|
||||
# (the same-sha rule); the body already reflects that commit.
|
||||
data["body_source"] = "forge"
|
||||
data["body_freshness"] = "current"
|
||||
return
|
||||
|
||||
cached = _normalized_code(fields.get("code") or "")
|
||||
if cached and cached in _normalized_code(fetched.content):
|
||||
data["body_source"] = "forge"
|
||||
|
||||
Reference in New Issue
Block a user