chadtmiller.com/knotless
git that lives in your Atmosphere account
1
10
A private repository's pusher's mirror takes in their pack as they push
crates/knotless-serve/src/gateway.rs+21−1
| @@ -679,10 +679,14 @@ pub async fn push( | ||
app.event("gateway.push", "", true, started.elapsed(), Some(&caller)); | ||
// By the owner it wrote for, whoever asked: what delegating led to, | ||
// once a ref moved. | ||
if report.iter().any(|(_, res)| res.is_ok()) { | ||
let moved = report.iter().any(|(_, res)| res.is_ok()); | ||
if moved { | ||
let owner = Some(remote.did()); | ||
app.event("gateway.served", "", true, started.elapsed(), owner); | ||
} | ||
if moved && remote.space.is_some() && !pack.is_empty() { | ||
seed(&app, &caller, remote.did(), &snap, body.slice_ref(pack)); | ||
} | ||
Ok(Json(PushResults { | ||
results: report | ||
.into_iter() | ||
| @@ -694,6 +698,22 @@ pub async fn push( | ||
})) | ||
} | ||
| ||
/// Puts `pack`, as `caller` pushed it to `did`'s private repository | ||
/// `snap`, in the mirror of it the pages keep for them, if they keep one: | ||
/// aside, as the push is answered. Their next look then reads its records | ||
/// and not this pack again from the PDS. Only what they sent goes in, | ||
/// theirs to read already, so their mirror is still theirs alone | ||
/// (`docs/design/access.md`). A pack whose bases that mirror lacks is not | ||
/// taken in, and that look fetches it as before. | ||
fn seed(app: &App, caller: &str, did: &str, snap: &Snapshot, pack: Bytes) { | ||
let key = snap.key().expect("a writable repository exists"); | ||
let ctx = crate::login::ctx_for(app, caller); | ||
let dir = knotless::mirror::path(&ctx, did, key); | ||
if dir.join("HEAD").exists() { | ||
tokio::spawn(async move { knotless::git::Git::new(dir).index_pack(&pack).await.ok() }); | ||
} | ||
} | ||
| ||
pub async fn commit( | ||
State(app): State<Arc<App>>, | ||
headers: HeaderMap, | ||
crates/knotless-serve/tests/gateway.rs+30−0
| @@ -805,12 +805,42 @@ fn private_repositories_through_a_gateway() { | ||
0 | ||
); | ||
| ||
// The collaborator reads it on the pages first, which keeps a mirror | ||
// of it for them alone. | ||
let cookie = session_cookie(&gw, &collab.account); | ||
let at = format!( | ||
"{}/xrpc/dev.knotless.getRepo?repo=at://{}/dev.knotless.repo/hidden", | ||
gw.base, owner.account.did | ||
); | ||
let (status, .., shown) = looked(&at, Some(&cookie)); | ||
assert_eq!(status, 200, "{shown}"); | ||
let underscored = |did: &str| did.replace(':', "_"); | ||
let theirs = (gw.app.ctx.env.cache_dir.join("web")) | ||
.join(underscored(&collab.account.did)) | ||
.join("mirrors") | ||
.join(underscored(&owner.account.did)); | ||
let mut kept = std::fs::read_dir(&theirs) | ||
.unwrap() | ||
.map(|e| e.unwrap().path()); | ||
let mirror = kept.find(|p| p.extension().is_some_and(|x| x == "git")); | ||
let mirror = mirror.expect("a mirror of it, theirs"); | ||
| ||
let clone = collab.path("clone"); | ||
collab.git(collab.dir.path(), &["clone", "-q", &url, "clone"]); | ||
let sha = collab.commit_file(&clone, "theirs", "private work\n"); | ||
collab.git(&clone, &["push", "-q", "origin", "main"]); | ||
let (_, refs) = owner.cli(&["repo", "refs", &url], b""); | ||
assert_eq!(refs, format!("{sha}\trefs/heads/main")); | ||
// What they pushed through the gateway is in their mirror before they | ||
// look again. | ||
let has = |git: &std::path::Path| { | ||
let found = std::process::Command::new("git") | ||
.args(["cat-file", "-e", &sha]) | ||
.current_dir(git) | ||
.status(); | ||
found.is_ok_and(|s| s.success()) | ||
}; | ||
wait_until(|| has(&mirror)); | ||
let (_, members) = owner.cli(&["repo", "members", &url], b""); | ||
assert_eq!( | ||
members, | ||
docs/design/access.md+7−4
| @@ -239,10 +239,13 @@ sequenceDiagram | ||
only to a member. | ||
- What is found is that member's alone: its mirror kept under their cache, | ||
and its entry under their DID, so nothing read for one viewer answers | ||
another. Its pulls, issues and checks are read from the server's own | ||
index of its space ([An index of every one read | ||
here](#an-index-of-every-one-read-here)), and a check's steps and log | ||
from the runner's repo in the space, as the member. | ||
another. A pack a member pushes through the gateway goes into their own | ||
mirror too, if they have one, as the push is answered, so their next | ||
look reads its records but not that pack again: what they sent was | ||
theirs to read already, and goes into no one else's. Its pulls, issues | ||
and checks are read from the server's own index of its space ([An index | ||
of every one read here](#an-index-of-every-one-read-here)), and a | ||
check's steps and log from the runner's repo in the space, as the member. | ||
- Membership is asked again, with a fresh credential, once the last answer | ||
is a minute old; in between, the space is read as it was found then, | ||
with no round trip to look for it again. Its mirror is synced again only | ||
docs/design/gateway.md+5−1
| @@ -163,7 +163,11 @@ as the owner, with the same | ||
naming the caller, from its verified token, as the `pusher` of each claim and | ||
ref it writes, unless the caller is the owner. It answers | ||
`results`, a `{name, error?}` per ref: each update lands or is refused on | ||
its own, and the client fails on the first one refused. | ||
its own, and the client fails on the first one refused. Once a ref of a | ||
private repository moved, the pack also goes into the caller's own mirror | ||
of it, if the pages keep one for them, aside: their next look on the pages | ||
then reads only its records | ||
([access.md](access.md#reading-a-private-repository)). | ||
| ||
## Runners landing a queue | ||