suture-cli 1.0.0

A patch-based version control system with semantic merge for structured files
pub(crate) async fn cmd_branch(
    name: Option<&str>,
    target: Option<&str>,
    delete: bool,
    list: bool,
    protect: bool,
    unprotect: bool,
) -> Result<(), Box<dyn std::error::Error>> {
    let mut repo = suture_core::repository::Repository::open(std::path::Path::new("."))?;

    if protect || unprotect {
        let branch_name = name.ok_or("--protect/--unprotect requires a branch name")?;
        let config_key = format!("branch.{}.protected", branch_name);
        if protect {
            repo.set_config(&config_key, "true")?;
        } else {
            let _ = repo.meta().delete_config(&config_key);
        }

        if let Ok(url) = repo.get_remote_url("origin") {
            let repo_id = crate::remote_proto::derive_repo_id(&url, "origin");
            let client = reqwest::Client::new();
            let endpoint = if protect { "protect" } else { "unprotect" };
            let resp = client
                .post(format!(
                    "{}/repos/{}/{}",
                    url.trim_end_matches('/'),
                    repo_id,
                    endpoint
                ))
                .send()
                .await?;
            if resp.status().is_success() {
                println!(
                    "Branch '{}' {} on remote",
                    branch_name,
                    if protect { "protected" } else { "unprotected" }
                );
            } else {
                eprintln!(
                    "Warning: could not {} branch on remote: {}",
                    if protect { "protect" } else { "unprotect" },
                    resp.status()
                );
            }
        }

        println!(
            "Branch '{}' {}",
            branch_name,
            if protect { "protected" } else { "unprotected" }
        );
        return Ok(());
    }

    if list || name.is_none() {
        let branches = repo.list_branches();
        if branches.is_empty() {
            println!("No branches.");
        } else {
            let head = repo.head().ok();
            let head_branch = head.as_ref().map(|(n, _)| n.as_str());
            for (bname, _target) in &branches {
                let marker = if head_branch == Some(bname.as_str()) {
                    "* "
                } else {
                    "  "
                };
                let protected = repo
                    .get_config(&format!("branch.{}.protected", bname))?
                    .is_some_and(|v| v == "true");
                let lock = if protected { " [protected]" } else { "" };
                println!("{}{}{}", marker, bname, lock);
            }
        }
        return Ok(());
    }

    let name =
        name.ok_or_else(|| "branch name required (use --list to show branches)".to_string())?;
    if delete {
        let branches: Vec<String> = repo.list_branches().into_iter().map(|(n, _)| n).collect();
        if !branches.iter().any(|b| b == name) {
            if let Some(suggestion) = crate::fuzzy::suggest(name, &branches) {
                return Err(format!(
                    "branch '{}' not found (did you mean '{}'?)",
                    name, suggestion
                )
                .into());
            } else {
                return Err(format!("branch '{}' not found", name).into());
            }
        }
        repo.delete_branch(name)?;
        println!("Deleted branch '{}'", name);
    } else {
        repo.create_branch(name, target)?;
        println!("Created branch '{}'", name);
    }
    Ok(())
}