grove-cli 0.1.5

Git worktree manager
use anyhow::{Context, Result};
use inquire::{Confirm, Select, Text};
use std::path::Path;

pub enum AddAction {
    Existing(String),
    New(String),
    Remote(String),
}

pub fn add_interactive(dir: &Path) -> Result<AddAction> {
    let action = Select::new(
        "Select action",
        vec!["existing branch", "new branch", "remote branch"],
    )
    .prompt()?;

    match action {
        "existing branch" => {
            let branches =
                grove_core::git::list_local_branches(dir).context("failed to list branches")?;
            let branch = Select::new("Select existing branch", branches).prompt()?;
            Ok(AddAction::Existing(branch))
        }
        "new branch" => {
            let branch = Text::new("Enter new branch name:").prompt()?;
            Ok(AddAction::New(branch))
        }
        "remote branch" => {
            grove_core::git::fetch_all(dir).context("fetch failed")?;
            let branches = grove_core::git::list_remote_branches(dir)
                .context("failed to list remote branches")?;
            let branch = Select::new("Select remote branch", branches).prompt()?;
            Ok(AddAction::Remote(branch))
        }
        _ => unreachable!(),
    }
}

pub fn switch_interactive(dir: &Path) -> Result<String> {
    let wts = grove_core::git::parse_worktree_list(dir).context("failed to list worktrees")?;

    let display_lines: Vec<String> = wts
        .iter()
        .map(|wt| {
            let short = grove_core::path::short_path(&wt.path);
            format!("{:30}  {}", wt.branch, short)
        })
        .collect();

    let selected = Select::new("Select worktree", display_lines).prompt()?;

    let branch = selected.split_whitespace().next().unwrap_or("").to_string();
    Ok(branch)
}

pub fn remove_interactive(dir: &Path) -> Result<String> {
    let wts = grove_core::git::parse_worktree_list(dir).context("failed to list worktrees")?;

    if wts.len() <= 1 {
        anyhow::bail!("no removable worktrees (main worktree cannot be removed)");
    }

    let display_lines: Vec<String> = wts
        .iter()
        .skip(1)
        .map(|wt| {
            let short = grove_core::path::short_path(&wt.path);
            let state = if wt.prunable { "  [prunable]" } else { "" };
            format!("{:30}  {}{}", wt.branch, short, state)
        })
        .collect();

    let selected = Select::new("Select worktree to remove", display_lines).prompt()?;
    let branch = selected.split_whitespace().next().unwrap_or("").to_string();

    let confirmed = Confirm::new(&format!("Remove worktree '{}'?", branch))
        .with_default(false)
        .prompt()?;

    if !confirmed {
        anyhow::bail!("cancelled");
    }

    Ok(branch)
}

pub fn cache_interactive() -> Result<String> {
    let action = Select::new("Cache action", vec!["link", "status", "unlink"]).prompt()?;
    Ok(action.to_string())
}