crates/gunnar-ui-web/src/first_push.rs
//! **The first push** (owner order 2026-10-18, wide audience): what a person
//! who has just made a repository, or found an empty one of theirs, is shown —
//! and what somebody who cannot make one is told instead. Host-side, so
//! `cargo test` covers the words the wasm pages draw.
//!
//! Nothing here decides a right. Whether this person may create, push or is on
//! a Free account is the server's answer (`/api/repos/creatable`,
//! `/api/repos/{ns}/{name}/rights`, the `free-cannot-push` item of
//! `/api/account/actions`); these functions only put it into words.
/// Where a person adds their SSH key: the SSH keys panel itself, not the top
/// of the account page.
pub const KEYS_HREF: &str = "/account#keys";
/// The server's queue item that says this account is Free and cannot push.
/// Its `href` and `cta` are the way to room (the server's words and door).
pub const FREE_ITEM: &str = "free-cannot-push";
/// The server's queue item that says this person has no SSH key yet.
pub const NO_KEY_ITEM: &str = "no-ssh-key";
/// What a Free account is told where the push commands would be.
pub const FREE_CANNOT_PUSH: &str =
"A Free account can read and clone, but cannot push. To push, your account needs room of its own.";
/// **The two commands that push an existing local repository here**, with the
/// real clone address and the repository's default branch (`refs/heads/main`
/// → `main`; `main` when it names none).
pub fn push_commands(clone_url: &str, default_branch: &str) -> [String; 2] {
let branch = default_branch.strip_prefix("refs/heads/").unwrap_or(default_branch).trim();
let branch = if branch.is_empty() { "main" } else { branch };
[format!("git remote add origin {clone_url}"), format!("git push -u origin {branch}")]
}
/// **Why "New repository" is not offered**, in plain words, for a signed-in
/// person the server gave nowhere to create.
///
/// * a member of an account: its admin makes repositories;
/// * a Free account (the server said so): its own room is the way;
/// * anybody else: whoever runs this Gunnar gives room.
pub fn why_no_create(member_of: Option<(&str, &str)>, free: bool) -> String {
match member_of {
Some((account, admin)) => {
let admin = if admin.is_empty() { account } else { admin };
format!("Repositories in {account} are created by its admin, {admin}. Ask them for a new one.")
}
None if free => "A Free account cannot create repositories: your account needs room of its own first.".to_owned(),
None => "This account has no room to create repositories in. Ask whoever runs this Gunnar for room.".to_owned(),
}
}
/// The empty catalogue, for someone signed in and for a visitor. A visitor is
/// never told to create or push: they cannot.
pub fn empty_catalogue(signed_in: bool) -> (&'static str, &'static str) {
if signed_in {
("No repositories yet", "Create one with New repository, then push to it.")
} else {
("No public repositories here yet", "Log in to see your own repositories.")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_push_commands_carry_the_real_address_and_branch() {
assert_eq!(
push_commands("ssh://alice@git.example.test:2222/alice/tool", "refs/heads/trunk"),
["git remote add origin ssh://alice@git.example.test:2222/alice/tool".to_owned(), "git push -u origin trunk".to_owned()]
);
assert_eq!(push_commands("u", "")[1], "git push -u origin main");
}
#[test]
fn the_key_link_goes_to_the_keys_panel_itself() {
assert_eq!(KEYS_HREF, "/account#keys");
}
#[test]
fn a_free_account_is_never_told_to_add_a_key_so_it_can_push() {
assert!(FREE_CANNOT_PUSH.contains("cannot push"));
assert!(!FREE_CANNOT_PUSH.contains("key"));
assert!(why_no_create(None, true).contains("Free"));
}
#[test]
fn a_member_is_told_whose_admin_creates() {
assert_eq!(
why_no_create(Some(("acme", "Ann")), false),
"Repositories in acme are created by its admin, Ann. Ask them for a new one."
);
assert!(why_no_create(Some(("acme", "")), false).contains("its admin, acme"));
assert!(why_no_create(None, false).contains("Ask whoever runs this Gunnar"));
}
#[test]
fn a_visitor_is_never_told_to_create_or_push() {
let (title, detail) = empty_catalogue(false);
for said in [title, detail] {
assert!(!said.contains("Create") && !said.contains("push") && !said.contains("SSH"), "{said}");
}
assert_eq!(empty_catalogue(true).0, "No repositories yet");
}
}