diff --git a/crates/forge/src/admin.rs b/crates/forge/src/admin.rs index 2db5338..2151eb7 100644 --- a/crates/forge/src/admin.rs +++ b/crates/forge/src/admin.rs @@ -103,11 +103,14 @@ pub fn repo_set_pull_floor(data_dir: &Path, owner: &str, name: &str, floor: i64) Ok(()) } -/// `admin org create`. -pub fn org_create(data_dir: &Path, name: &str, owner: &str) -> Result<()> { +/// `admin org create`, with Gitea's visibility values (`public`, `limited`, `private`). +pub fn org_create(data_dir: &Path, name: &str, owner: &str, visibility: &str) -> Result<()> { if names::check(name).is_err() { bail!("invalid or reserved organization name {name:?}"); } + if !crate::api::valid_visibility(visibility) { + bail!("invalid visibility {visibility:?}: use public, limited or private"); + } let mut conn = db::open_for_admin(data_dir)?; let owner = store::find_by_login(&conn, owner)? .filter(|u| !u.is_org) @@ -115,7 +118,7 @@ pub fn org_create(data_dir: &Path, name: &str, owner: &str) -> Result<()> { let acct = NewAccount { login: name.into(), is_org: true, - visibility: "public".into(), + visibility: visibility.into(), ..Default::default() }; match store::create_org(&mut conn, &acct, owner.id, now()) { diff --git a/crates/forge/src/cli.rs b/crates/forge/src/cli.rs index 46a8b52..8ab5126 100644 --- a/crates/forge/src/cli.rs +++ b/crates/forge/src/cli.rs @@ -192,6 +192,9 @@ enum OrgCmd { name: String, #[arg(long)] owner: String, + /// `public`, `limited` or `private`, as in Gitea. + #[arg(long, default_value = "public")] + visibility: String, #[arg(long)] data_dir: PathBuf, }, @@ -272,10 +275,11 @@ fn run_admin(cmd: AdminCmd) -> Result<()> { OrgCmd::Create { name, owner, + visibility, data_dir, }, } => { - admin::org_create(&data_dir, &name, &owner)?; + admin::org_create(&data_dir, &name, &owner, &visibility)?; println!("Organization '{name}' has been successfully created!"); } AdminCmd::Repo { diff --git a/crates/forge/src/protection.rs b/crates/forge/src/protection.rs index f1535eb..0b9ed56 100644 --- a/crates/forge/src/protection.rs +++ b/crates/forge/src/protection.rs @@ -159,8 +159,9 @@ fn is_default(v: &Value) -> bool { } } +/// Does `s` use Gitea's glob syntax (its compiler gives `* ? [ { \\` a meaning)? fn has_glob(s: &str) -> bool { - s.contains(['*', '?', '[']) + s.contains(['*', '?', '[', '{', '\\']) } fn contexts_of(v: &Value) -> Result>, String> { diff --git a/crates/forge/tests/e2e_a.rs b/crates/forge/tests/e2e_a.rs index dbaee29..2502c1b 100644 --- a/crates/forge/tests/e2e_a.rs +++ b/crates/forge/tests/e2e_a.rs @@ -650,8 +650,12 @@ fn admin_cli_changes_are_visible_to_the_running_server() { .status, 201 ); - scl_forge::admin::org_create(&s.dir, "cliorg", "carol").unwrap(); + scl_forge::admin::org_create(&s.dir, "cliorg", "carol", "public").unwrap(); assert_eq!(s.get("/api/v1/orgs/cliorg", &token(&s.admin)).status, 200); + scl_forge::admin::org_create(&s.dir, "hidden", "carol", "private").unwrap(); + let r = s.get("/api/v1/orgs/hidden", &token(&s.admin)); + assert_eq!(r.json()["visibility"], "private"); + assert!(scl_forge::admin::org_create(&s.dir, "odd", "carol", "secret").is_err()); assert!(scl_forge::admin::user_create( &s.dir, "carol", diff --git a/crates/forge/tests/e2e_protection.rs b/crates/forge/tests/e2e_protection.rs index 0eb561f..0c22612 100644 --- a/crates/forge/tests/e2e_protection.rs +++ b/crates/forge/tests/e2e_protection.rs @@ -163,6 +163,12 @@ fn rule_crud_validation_and_the_branch_object() { 422, "patterns are not supported", ), + ( + "brace glob", + json!({"branch_name": "{main,release}"}), + 422, + "patterns are not supported", + ), ( "approvals", json!({"branch_name": "x", "required_approvals": 2}), diff --git a/docs/superpowers/specs/2026-10-22-p38-forge-design.md b/docs/superpowers/specs/2026-10-22-p38-forge-design.md index 1924ff5..d553393 100644 --- a/docs/superpowers/specs/2026-10-22-p38-forge-design.md +++ b/docs/superpowers/specs/2026-10-22-p38-forge-design.md @@ -510,7 +510,7 @@ sc-forge serve --data-dir DIR --listen ADDR --public-url URL [--behind-proxy|--a sc-forge admin user create --username U --password P --email E [--admin] [--must-change-password=false] sc-forge admin user change-password --username U --password P [--must-change-password=false] sc-forge admin user generate-access-token --username U [--token-name N] [--scopes S,…] [--raw] -sc-forge admin org create --name O --owner U +sc-forge admin org create --name O --owner U [--visibility public|limited|private] sc-forge hook pre-receive|post-receive # internal ```