OXYGENHUB

A wiki can start with a push 6e7dfa0a42b9

When someone who may write to a repository reaches OWNER/NAME.wiki
before the wiki exists, the hub makes it, empty and unbound, so the
first pages can be pushed as well as written on the web; the push gives
it its id. Readers still find nothing there. Test
a_wiki_can_start_with_a_push.
Author
Andy McCall @andymccall <[email protected]>
When
· 2026-10-05 12:12 UTC
Signature
✔ signed by Andy McCall
Hash
6e7dfa0a42b996d4521e15039a06833f4efe8073bc815ee731b6233b2cf8aefa
Depends on
145306044896 66fd9d6b1452 749d9a5fcd9f 8475176972e8 b5668108487f d23464c0ac60
Depended on by
c8684d44d549 ffb67a81188c 554ccacfc123
In bookmarks
api-unknown-fields cancel-landed-runs clear-waiting close-proposal-140 diffusion-pin-bookmark draft-ready image-check job-waits main oh-comment-body-file proposal-list-fast run-one-job wiki-says-so
In tags
v0.4.1 v0.4.2 v0.5.0 v0.5.1 v0.5.2 v0.6.0 v0.6.1 v0.7.0 v0.8.0 v0.9.0 v0.9.1 v0.9.11 v0.9.15 v0.9.16 v0.9.19 v0.9.2 v0.9.20 v0.9.21 v0.9.22 v0.9.23 v0.9.24 v0.9.3 v0.9.4 v0.9.5 v0.9.6 v0.9.7 v0.9.8 v0.9.9
Edits
3 atoms, 0 tree operations, 3 files

Browse the tree at this change

modified CHANGELOG.md +2 −0
…
3 unchanged lines
4 4
5 5
## Unreleased
6 6
7 +
- **A wiki can start with a push.** Someone who may write to a repository can push the first pages of its wiki to `OWNER/NAME.wiki`, as well as writing them on the web: the hub makes the empty wiki when a writer first reaches that address, and the push gives it its id. Readers still find nothing there until it has pages.
8 +
7 9
- **Mermaid diagrams in Markdown.** A ```` ```mermaid ```` block in a README, wiki page, issue or proposal is drawn as a diagram, in the page's light or dark theme. The hub serves its own copy of Mermaid 12.1.0, loads it only on pages that have a diagram, and runs it in strict mode. A diagram that does not parse stays as its source, with the reason under it.
8 10
9 11
- **An organisation's storage policy (#96).** An organisation's Settings › Storage can require every new repository to live in a bucket and list the buckets allowed, so its data stays where its own buckets are. The New repository form refuses anything else and says what is allowed.
…
125 unchanged lines
modified crates/hub/src/protocol.rs +18 −0
…
164 unchanged lines
165 165
        if !auth::roles::allows(role, Access::Read) {
166 166
            return Err(not_found());
167 167
        }
168 +
        // A wiki is made, empty and unbound, when someone who may write to
169 +
        // its repository first reaches it, so the first page can be pushed
170 +
        // as well as written on the web (§6.11); the push gives it its id.
171 +
        let (_, is_wiki) = crate::wiki::parent_of(&parsed);
172 +
        let may_write = match &caller {
173 +
            Caller::Site => true,
174 +
            Caller::Token(_, token) => token.has_scope("repo:write") || token.has_scope("repo:admin"),
175 +
            _ => false,
176 +
        };
177 +
        if is_wiki && may_write && auth::roles::allows(role, Access::Write) && !row.archived {
178 +
            if let Some(name) = RepoName::new(&row.owner, &row.name) {
179 +
                if !self.wikis.exists(&name) {
180 +
                    self.wikis
181 +
                        .create(&name, None)
182 +
                        .map_err(|e| Failure(StatusCode::INTERNAL_SERVER_ERROR, format!("make the wiki: {e}")))?;
183 +
                }
184 +
            }
185 +
        }
168 186
        match access {
169 187
            oxy_server::Access::Read if scope_ok => Ok(()),
170 188
            oxy_server::Access::Read => Err(Failure(StatusCode::FORBIDDEN, "this token cannot read repositories".into())),
…
184 unchanged lines
modified crates/hub/tests/edits.rs +47 −0
…
791 unchanged lines
792 792
    assert!(r.body.contains("The wiki is in the trash"), "{}", r.body);
793 793
    assert!(send(app, "GET", "/andy/demo/wiki", Some(&andy), None).await.body.contains("No wiki yet"));
794 794
}
795 +
796 +
/// A wiki can start with a push: someone who may write to the repository
797 +
/// reaching `OWNER/NAME.wiki` before it exists gets an empty wiki, which
798 +
/// the push fills; a reader still finds nothing there.
799 +
#[tokio::test(flavor = "multi_thread")]
800 +
async fn a_wiki_can_start_with_a_push() {
801 +
    hermetic();
802 +
    let dir = tempfile::tempdir().unwrap();
803 +
    let mut config = Config::load(dir.path()).unwrap();
804 +
    config.auth.push_tokens = vec!["site".into()];
805 +
    let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
806 +
    let port = listener.local_addr().unwrap().port();
807 +
    config.server.base_url = format!("http://127.0.0.1:{port}");
808 +
    let app = oxygenhub::build(config).await.unwrap();
809 +
    let router = oxygenhub::router(app.clone());
810 +
    tokio::spawn(async move { axum::serve(listener, router).await.unwrap() });
811 +
    let base = format!("http://127.0.0.1:{port}");
812 +
    let repo = RepoName::parse("andy/docs").unwrap();
813 +
    app.repos.create(&repo, None).unwrap();
814 +
    db::repos::create(&app.db, "andy", "docs", "", hub_api::Visibility::Public).await.unwrap();
815 +
    let url = format!("{base}/andy/docs.wiki");
816 +
817 +
    // A reader: nothing there, and nothing made.
818 +
    let anon = tempfile::tempdir().unwrap();
819 +
    let u = url.clone();
820 +
    let p = anon.path().join("w");
821 +
    assert!(tokio::task::spawn_blocking(move || Repo::clone(&u, &p)).await.unwrap().is_err());
822 +
    assert!(!app.wikis.exists(&repo));
823 +
824 +
    // A writer pushes pages it wrote locally.
825 +
    let work = tempfile::tempdir().unwrap();
826 +
    let mut w = Repo::init(work.path()).unwrap();
827 +
    w.config.set("remote.origin.url", &url).unwrap();
828 +
    w.config.set("remote.origin.token", "site").unwrap();
829 +
    w.save_config().unwrap();
830 +
    write(work.path(), "Home.md", b"# Docs\n\nSee [the overview](Overview).\n");
831 +
    write(work.path(), "Overview.md", b"# Overview\n\n```mermaid\nflowchart LR\n  A --> B\n```\n");
832 +
    w.snapshot("t").unwrap();
833 +
    w.commit("First pages", &[]).unwrap();
834 +
    let pushed = tokio::task::spawn_blocking(move || w.push("origin", false).unwrap()).await.unwrap();
835 +
    assert!(pushed.iter().all(|r| r.ok), "{pushed:?}");
836 +
    assert!(app.wikis.exists(&repo));
837 +
    let home = send(&app, "GET", "/andy/docs/wiki", None, None).await.body;
838 +
    assert!(home.contains("<h1>Docs</h1>") && home.contains("href=\"/andy/docs/wiki/Overview\""), "{home}");
839 +
    let overview = send(&app, "GET", "/andy/docs/wiki/Overview", None, None).await.body;
840 +
    assert!(overview.contains("class=\"language-mermaid\""), "a diagram block for the page's script to draw: {overview}");
841 +
}