eazygit 0.5.1

A fast TUI for Git with staging, conflicts, rebase, and palette-first UX
Documentation
//! Initialization and helper functions for the event loop.

use crate::app::{Action, reducer, Application};
use crate::app::workflow::WorkflowState;

/// Initialize all data sources during application startup.
/// These operations are non-critical and failures are logged but don't block startup.
pub fn initialize_data_sources(app: &mut Application) {
    let data_sources = [
        ("branches", Action::RefreshBranches),
        ("commits", Action::RefreshCommits),
        ("stashes", Action::RefreshStashes),
        ("tags", Action::RefreshTags),
        ("reflog", Action::RefreshReflog),
    ];
    
    for (source_name, action) in data_sources {
        if let Err(e) = app.component_manager.update(action, &mut app.state) {
            tracing::warn!(
                source = source_name,
                error = %e,
                "Failed to initialize {} during startup (non-critical, continuing)",
                source_name
            );
        }
    }
}

/// Information about a rebase recovery state.
pub struct RebaseRecoveryInfo {
    pub user_message: String,
    pub log_message: String,
}

/// Detect detailed state of an interrupted rebase for recovery messaging.
/// Stubbed - rebase feature removed.
pub fn detect_rebase_recovery_state(_repo_path: &str) -> RebaseRecoveryInfo {
    RebaseRecoveryInfo {
        user_message: String::new(),
        log_message: String::new(),
    }
}

/// Handle rebase recovery on startup.
pub fn handle_rebase_recovery(app: &mut Application) {
    if matches!(
        app.state.workflow_context.as_ref().map(|ctx| ctx.state.clone()),
        Some(WorkflowState::RebaseInProgress)
    ) {
        let recovery_info = detect_rebase_recovery_state(&app.state.repo_path);
        
        match app.git_service.read_rebase_todo(&app.state.repo_path) {
            Ok((lines, _path)) => {
                app.state = reducer(app.state.clone(), Action::RebaseLoadTodo(lines));
                app.state = reducer(app.state.clone(), Action::SetFeedback(Some(recovery_info.user_message)));
                app.state = reducer(app.state.clone(), Action::AppendOpLog(recovery_info.log_message));
            }
            Err(e) => {
                tracing::warn!(error = %e, "Failed to load existing rebase todo on startup");
                app.state = reducer(app.state.clone(), Action::SetFeedback(Some(recovery_info.user_message)));
                app.state = reducer(app.state.clone(), Action::AppendOpLog(format!("Recovered rebase (todo load failed: {})", e)));
            }
        }
    }
}

/// Preflight check for amend operations.
pub fn preflight_amend(state: &crate::app::AppState) -> Result<(), String> {
    if let Some(ctx) = &state.workflow_context {
        match ctx.state {
            WorkflowState::RebaseInProgress
            | WorkflowState::CherryPickInProgress
            | WorkflowState::MergeInProgress
            | WorkflowState::Conflicts => {
                return Err("Cannot amend while rebase/cherry-pick/merge/conflicts are in progress".to_string());
            }
            _ => {}
        }
    }

    let has_unstaged_or_conflict = state.status_entries.iter().any(|e| e.unstaged || e.conflict);
    if has_unstaged_or_conflict {
        return Err("Amend requires a clean working tree (no unstaged or conflict files)".to_string());
    }

    let has_untracked = state.status_entries.iter().any(|e| !e.staged && !e.unstaged && !e.conflict);
    if has_untracked {
        return Err("Amend requires no untracked files; clean or add them first".to_string());
    }

    Ok(())
}

/// Preflight check for cherry-pick and revert operations.
pub fn preflight_pick_revert(state: &crate::app::AppState, op: &str) -> Result<(), String> {
    if let Some(ctx) = &state.workflow_context {
        match ctx.state {
            WorkflowState::RebaseInProgress
            | WorkflowState::CherryPickInProgress
            | WorkflowState::MergeInProgress
            | WorkflowState::Conflicts => {
                return Err(format!("{op} blocked: rebase/cherry-pick/merge/conflicts in progress"));
            }
            _ => {}
        }
    }

    if !state.status_entries.is_empty() {
        return Err(format!("{op} requires a clean working tree (no staged/unstaged/untracked/conflict files)"));
    }

    Ok(())
}

/// Validate amend input before execution.
pub fn validate_amend_input(state: &crate::app::AppState) -> Result<(), String> {
    let has_message = !state.commit_input.trim().is_empty();
    let has_name = !state.amend_author_name.trim().is_empty();
    let has_email = !state.amend_author_email.trim().is_empty();
    
    if !has_message && !has_name && !has_email {
        return Ok(()); // Allow empty - git will re-edit
    }
    
    if has_name != has_email {
        return Err("Author name and email must both be provided together, or leave both empty".to_string());
    }
    
    Ok(())
}