eazygit 0.5.1

A fast TUI for Git with staging, conflicts, rebase, and palette-first UX
Documentation
use crate::app::Action;

/// Types of git errors that can occur
#[derive(Debug, Clone, PartialEq)]
#[allow(dead_code)]
pub enum ErrorType {
    UncommittedChanges,
    MergeConflict,
    AuthenticationFailed,
    RemoteConnectionFailed,
    BranchNotFound,
    CommitNotFound,
    RebaseInProgress,
    MergeInProgress,
    StashNotFound,
    InvalidOperation,
    Unknown,
}

/// A suggested action to resolve an error
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct ErrorAction {
    pub key: String,
    pub label: String,
    pub action: Option<Action>,
}

/// Error guidance with actionable suggestions
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct ErrorGuidance {
    pub error_type: ErrorType,
    pub message: String,
    pub suggestions: Vec<String>,
    pub next_actions: Vec<ErrorAction>,
}

impl ErrorGuidance {
    /// Create error guidance from an error message
    pub fn from_error(error_msg: &str) -> Self {
        let error_lower = error_msg.to_lowercase();
        
        // Detect error type
        let error_type = if error_lower.contains("uncommitted changes") 
            || error_lower.contains("local changes would be overwritten") {
            ErrorType::UncommittedChanges
        } else if error_lower.contains("merge conflict") 
            || error_lower.contains("conflicts") {
            ErrorType::MergeConflict
        } else if error_lower.contains("authentication") 
            || error_lower.contains("permission denied") {
            ErrorType::AuthenticationFailed
        } else if error_lower.contains("could not resolve host")
            || error_lower.contains("connection refused")
            || error_lower.contains("network") {
            ErrorType::RemoteConnectionFailed
        } else if error_lower.contains("branch") && error_lower.contains("not found") {
            ErrorType::BranchNotFound
        } else if error_lower.contains("commit") && error_lower.contains("not found") {
            ErrorType::CommitNotFound
        } else if error_lower.contains("rebase") && error_lower.contains("in progress") {
            ErrorType::RebaseInProgress
        } else if error_lower.contains("merge") && error_lower.contains("in progress") {
            ErrorType::MergeInProgress
        } else if error_lower.contains("stash") && error_lower.contains("not found") {
            ErrorType::StashNotFound
        } else {
            ErrorType::Unknown
        };
        
        // Generate guidance based on error type
        let (suggestions, next_actions) = match error_type {
            ErrorType::UncommittedChanges => (
                vec![
                    "You have uncommitted changes that would be overwritten".to_string(),
                    "Commit your changes first, or stash them to save for later".to_string(),
                ],
                vec![
                    ErrorAction {
                        key: "c".to_string(),
                        label: "Commit changes".to_string(),
                        action: Some(Action::StartCommit),
                    },
                    ErrorAction {
                        key: "u".to_string(),
                        label: "Unstage changes".to_string(),
                        action: Some(Action::UnstageAllFiles),
                    },
                ],
            ),
            ErrorType::MergeConflict => (
                vec![
                    "Merge conflicts detected in your files".to_string(),
                    "Resolve conflicts manually or use guided resolution".to_string(),
                ],
                vec![
                    ErrorAction {
                        key: "g".to_string(),
                        label: "Open guided resolution".to_string(),
                        action: Some(Action::ShowConflictsGuided),
                    },
                    ErrorAction {
                        key: "o".to_string(),
                        label: "Open conflicts in editor".to_string(),
                        action: None, // Will use OpenConflictInEditor with path when available
                    },
                ],
            ),
            ErrorType::AuthenticationFailed => (
                vec![
                    "Authentication failed with remote repository".to_string(),
                    "Check your credentials or SSH keys".to_string(),
                ],
                vec![],
            ),
            ErrorType::RemoteConnectionFailed => (
                vec![
                    "Cannot connect to remote repository".to_string(),
                    "Check your internet connection and remote URL".to_string(),
                ],
                vec![],
            ),
            ErrorType::BranchNotFound => (
                vec![
                    "Branch not found".to_string(),
                    "Check branch name or fetch from remote".to_string(),
                ],
                vec![
                    ErrorAction {
                        key: "f".to_string(),
                        label: "Check auto-fetch".to_string(),
                        action: Some(Action::CheckAutoFetch),
                    },
                ],
            ),
            ErrorType::CommitNotFound => (
                vec![
                    "Commit not found".to_string(),
                    "Check commit hash or fetch from remote".to_string(),
                ],
                vec![],
            ),
            ErrorType::RebaseInProgress => (
                vec![
                    "A rebase is already in progress".to_string(),
                    "Continue, abort, or skip the current rebase step".to_string(),
                ],
                vec![
                    ErrorAction {
                        key: "rc".to_string(),
                        label: "Continue rebase".to_string(),
                        action: Some(Action::RebaseContinue),
                    },
                    ErrorAction {
                        key: "ra".to_string(),
                        label: "Abort rebase".to_string(),
                        action: Some(Action::RebaseAbort),
                    },
                ],
            ),
            ErrorType::MergeInProgress => (
                vec![
                    "A merge is already in progress".to_string(),
                    "Resolve conflicts and commit, or abort the merge".to_string(),
                ],
                vec![],
            ),
            ErrorType::StashNotFound => (
                vec![
                    "Stash not found".to_string(),
                    "Check stash name or list available stashes".to_string(),
                ],
                vec![],
            ),
            ErrorType::InvalidOperation | ErrorType::Unknown => (
                vec![
                    "An error occurred".to_string(),
                    "Check the error message above for details".to_string(),
                ],
                vec![],
            ),
        };
        
        Self {
            error_type,
            message: error_msg.to_string(),
            suggestions,
            next_actions,
        }
    }
    
    /// Format error guidance for display
    pub fn format_display(&self) -> String {
        let mut lines = vec![self.message.clone()];
        lines.push(String::new());
        
        if !self.suggestions.is_empty() {
            lines.push("Suggestions:".to_string());
            for suggestion in &self.suggestions {
                lines.push(format!("{}", suggestion));
            }
        }
        
        if !self.next_actions.is_empty() {
            lines.push(String::new());
            lines.push("Quick actions:".to_string());
            for action in &self.next_actions {
                lines.push(format!("  [{}] {}", action.key, action.label));
            }
        }
        
        lines.join("\n")
    }
}