gitsw 0.1.0

A smart Git branch switcher with automatic stash management and dependency installation
Documentation
use anyhow::{anyhow, Result};
use dialoguer::{theme::ColorfulTheme, Confirm, FuzzySelect, Select};

use crate::git::GitRepo;
use crate::state::StateManager;

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum StashAction {
    Stash,
    Discard,
    Abort,
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum UnstashAction {
    Apply,
    Skip,
    Abort,
}

/// Interactive branch selector with fuzzy search
pub fn select_branch() -> Result<String> {
    let repo = GitRepo::open()?;
    let state = StateManager::load(repo.git_dir())?;
    let all_branches = repo.list_branches()?;

    if all_branches.is_empty() {
        return Err(anyhow!("No branches found"));
    }

    let current = repo.get_current_branch().ok();

    // Get recent branches from state
    let recent = state.recent_branches(20);
    let recent_names: Vec<&str> = recent.iter().map(|(name, _)| *name).collect();

    // Sort branches: recent first, then alphabetically
    let mut branches: Vec<String> = Vec::new();

    // Add recent branches first (that still exist)
    for name in &recent_names {
        if all_branches.contains(&name.to_string()) {
            branches.push(name.to_string());
        }
    }

    // Add remaining branches alphabetically
    for branch in &all_branches {
        if !branches.contains(branch) {
            branches.push(branch.clone());
        }
    }

    // Mark current branch in the list
    let display_branches: Vec<String> = branches
        .iter()
        .map(|b| {
            if Some(b.as_str()) == current.as_deref() {
                format!("{} (current)", b)
            } else {
                b.clone()
            }
        })
        .collect();

    let selection = FuzzySelect::with_theme(&ColorfulTheme::default())
        .with_prompt("Select branch")
        .items(&display_branches)
        .default(0)
        .max_length(15)
        .interact()?;

    Ok(branches[selection].clone())
}

/// Prompt user when there are uncommitted changes
pub fn prompt_stash_conflict(changes_summary: &str) -> Result<StashAction> {
    println!();
    let items = vec![
        "Stash changes (recommended)",
        "Discard changes (lose all modifications)",
        "Abort switch",
    ];

    let selection = Select::with_theme(&ColorfulTheme::default())
        .with_prompt(format!(
            "You have uncommitted changes ({}).\nWhat would you like to do?",
            changes_summary
        ))
        .items(&items)
        .default(0)
        .interact()?;

    Ok(match selection {
        0 => StashAction::Stash,
        1 => StashAction::Discard,
        _ => StashAction::Abort,
    })
}

/// Prompt user when stash application has conflicts
pub fn prompt_unstash_conflict() -> Result<UnstashAction> {
    println!();
    let items = vec![
        "Try to apply anyway (may have conflicts)",
        "Skip restoring changes",
        "Abort and stay on current branch",
    ];

    let selection = Select::with_theme(&ColorfulTheme::default())
        .with_prompt(
            "There was an issue applying your stashed changes.\nWhat would you like to do?",
        )
        .items(&items)
        .default(0)
        .interact()?;

    Ok(match selection {
        0 => UnstashAction::Apply,
        1 => UnstashAction::Skip,
        _ => UnstashAction::Abort,
    })
}

/// Prompt user to run package install
pub fn prompt_install(package_manager: &str) -> Result<bool> {
    println!();
    Confirm::with_theme(&ColorfulTheme::default())
        .with_prompt(format!(
            "Lock file has changed. Run `{} install`?",
            package_manager
        ))
        .default(true)
        .interact()
        .map_err(Into::into)
}

/// Prompt for confirmation before discarding changes
pub fn confirm_discard() -> Result<bool> {
    Confirm::with_theme(&ColorfulTheme::default())
        .with_prompt("Are you sure you want to discard all changes? This cannot be undone.")
        .default(false)
        .interact()
        .map_err(Into::into)
}

/// Prompt for confirmation before deleting a branch
pub fn confirm_delete(branch_name: &str) -> Result<bool> {
    Confirm::with_theme(&ColorfulTheme::default())
        .with_prompt(format!("Delete branch '{}'?", branch_name))
        .default(false)
        .interact()
        .map_err(Into::into)
}