fix: request_apply_commit resolves sha locally + rejects non-sha refs

This commit is contained in:
damocles 2026-05-20 09:48:05 +02:00
parent 5d27ae3048
commit f8795dc029
6 changed files with 130 additions and 17 deletions

View file

@ -559,17 +559,49 @@ async fn git(dir: &Path, args: &[&str]) -> Result<()> {
Ok(())
}
/// Fetch `sha` from the `src` git repo into `dst` and pin it as
/// `refs/tags/<tag>`. Used at `request_apply_commit` time so hive-c0re
/// captures an immutable handle on the manager's commit; subsequent
/// amendments / force-pushes in `src` no longer affect what gets
/// built. Returns the resolved sha (which equals `sha` on success
/// but normalised — short shas get expanded).
/// Fetch the commit `sha` from the `src` git repo into `dst` and pin
/// it as `refs/tags/<tag>`. Used at `request_apply_commit` time so
/// hive-c0re captures an immutable handle on the manager's commit;
/// subsequent amendments / force-pushes in `src` no longer affect
/// what gets built. Returns the resolved full sha.
///
/// `sha` must be a commit sha (short or full) — the caller
/// (`submit_apply_commit`) shape-checks it first. We resolve it
/// LOCALLY against `src` rather than asking the remote to resolve
/// it: `git fetch <remote> <sha>:<dst>` treats the left side as a
/// remote *ref name*, and a bare sha is not one ("couldn't find
/// remote ref ..."). Fetching by sha would need a full 40-hex sha
/// plus `uploadpack.allow*SHA1InWant` on the remote, which the
/// proposed repos don't set. hive-c0re has direct read access to
/// `src`, so a local `rev-parse` + a branch-glob fetch sidesteps
/// the whole sha-want negotiation.
pub async fn git_fetch_to_tag(dst: &Path, src: &Path, sha: &str, tag: &str) -> Result<String> {
let src_str = src.display().to_string();
let refspec = format!("{sha}:refs/tags/{tag}");
git(dst, &["fetch", "--no-tags", &src_str, &refspec]).await?;
git_rev_parse(dst, &format!("refs/tags/{tag}")).await
// Resolve the (short-or-full) sha to a full sha against the
// source repo. The `^{commit}` peel + non-zero exit on a missing
// object means a typo'd / stale sha fails loudly right here.
let full = git_rev_parse(src, &format!("{sha}^{{commit}}"))
.await
.with_context(|| format!("commit '{sha}' not found in proposed repo {src_str}"))?;
// Bring src's objects into dst. Fetching every head pulls the
// wanted commit's history (always reachable from a branch in the
// manager's flow) into dst's object db without sha-want.
git(
dst,
&[
"fetch",
"--no-tags",
&src_str,
"+refs/heads/*:refs/remotes/proposal-src/*",
],
)
.await?;
// Pin the exact commit as the proposal tag. The objects are now
// local so this resolves without touching the remote.
git(dst, &["tag", tag, &full]).await.with_context(|| {
format!("tag {tag} at {full}: commit not reachable from any branch in proposed repo")
})?;
Ok(full)
}
/// Resolve `refname` (a tag, branch, or sha) in `dir` to its full sha.