From 2df65b74413c29e2d7fe10ef9a1b7578926bd124 Mon Sep 17 00:00:00 2001 From: Lewis Date: Tue, 28 Jul 2026 14:13:48 +0000 Subject: [PATCH] knot2/xrpc: hidden staging ref read Lewis: May this revision serve well! --- knot2/crates/knot-git/src/repo.rs | 10 ++++++++++ knot2/crates/knot-xrpc/src/reads.rs | 11 +++++++++++ knot2/crates/knot-xrpc/tests/reads.rs | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 file(s) changed, 92 insertion(s)(+), 0 deletion(s)(-) diff --git a/knot2/crates/knot-git/src/repo.rs b/knot2/crates/knot-git/src/repo.rs --- a/knot2/crates/knot-git/src/repo.rs +++ b/knot2/crates/knot-git/src/repo.rs @@ -566,6 +566,16 @@ None => Ok(None), } } + pub fn hidden_ref_commit(&self, spec: &str) -> Option { + let name = match spec.starts_with("refs/") { + true => RefName::new(spec.to_string()), + false => RefName::new(format!("refs/{spec}")), + } + .ok() + .filter(is_hidden)?; + self.find_ref(&name).ok().flatten() + } + fn direct_target(&self, reference: &gix::Reference<'_>, depth: usize) -> Option { match (depth, reference.follow()) { (_, None) => reference.try_id().map(|id| id.detach()), diff --git a/knot2/crates/knot-xrpc/src/reads.rs b/knot2/crates/knot-xrpc/src/reads.rs --- a/knot2/crates/knot-xrpc/src/reads.rs +++ b/knot2/crates/knot-xrpc/src/reads.rs @@ -137,11 +137,19 @@ false => repo.resolve_revision(refspec), } .ok_or_else(ref_not_found)?; let commit = repo.peel_to_commit(oid).map_err(|_| ref_not_found())?; + if hidden_staging_commit(repo, refspec) == Some(commit) { + return Ok(commit); + } match repo.reachable_from_public(commit) { Ok(true) => Ok(commit), Ok(false) => Err(ref_not_found()), Err(error) => Err(error.into()), } +} + +fn hidden_staging_commit(repo: &Repo, refspec: &str) -> Option { + repo.hidden_ref_commit(refspec) + .and_then(|oid| repo.peel_to_commit(oid).ok()) } struct LimitWriter { @@ -989,6 +997,9 @@ let commit = repo .resolve_revision(rev) .and_then(|oid| repo.peel_to_commit(oid).ok()) .ok_or_else(revision_not_found)?; + if hidden_staging_commit(&repo, rev) == Some(commit) { + return Ok(commit); + } match repo.reachable_from_public(commit) { Ok(true) => Ok(commit), Ok(false) => Err(revision_not_found()), diff --git a/knot2/crates/knot-xrpc/tests/reads.rs b/knot2/crates/knot-xrpc/tests/reads.rs --- a/knot2/crates/knot-xrpc/tests/reads.rs +++ b/knot2/crates/knot-xrpc/tests/reads.rs @@ -894,6 +894,77 @@ assert!(repos["cursor"].is_null()); } #[tokio::test] +async fn a_hidden_staging_ref_resolves_for_fork_comparison_reads() { + let world = World::new(); + let (did, work) = seeded(&world, "limpet"); + let bare = world.layout.repo_path(&did).unwrap(); + + sh_git(work.path(), &["checkout", "-q", "-b", "upstream"]); + commit_file( + work.path(), + "upstream.txt", + b"upstream\n", + "upstream moved", + "2026-06-01T12:50:00+02:00", + ); + let upstream = sh_git(work.path(), &["rev-parse", "HEAD"]); + sh_git( + work.path(), + &[ + "push", + "-q", + bare.to_str().unwrap(), + "HEAD:refs/hidden/main/main", + ], + ); + sh_git(work.path(), &["checkout", "-q", "main"]); + commit_file( + work.path(), + "ours.txt", + b"ours\n", + "fork work", + "2026-06-01T12:55:00+02:00", + ); + sh_git( + work.path(), + &["push", "-q", bare.to_str().unwrap(), "HEAD:refs/heads/main"], + ); + + let comparison = get_json( + &world, + &format!("/xrpc/sh.tangled.repo.compare?repo={did}&rev1=hidden/main/main&rev2=main"), + ) + .await; + assert_eq!(comparison["rev1"].as_str().unwrap(), upstream); + assert!(!comparison["format_patch"].as_array().unwrap().is_empty()); + + let log = get_json( + &world, + &format!("/xrpc/sh.tangled.repo.log?repo={did}&ref=refs/hidden/main/main"), + ) + .await; + assert!(!log["commits"].as_array().unwrap().is_empty()); + + let (status, _) = get_error( + &world, + &format!("/xrpc/sh.tangled.repo.log?repo={did}&ref={upstream}"), + ) + .await; + assert_eq!( + status, + StatusCode::NOT_FOUND, + "a raw oid reachable only through the hidden ref mustn't resolve" + ); + let (status, error) = get_error( + &world, + &format!("/xrpc/sh.tangled.repo.compare?repo={did}&rev1={upstream}&rev2=main"), + ) + .await; + assert_eq!(status, StatusCode::BAD_REQUEST); + assert_eq!(error, "RevisionNotFound"); +} + +#[tokio::test] async fn the_cob_ref_namespace_is_invisible_across_every_read() { let world = World::new(); let (did, work) = seeded(&world, "anemone"); -- tangled.sh