eazygit 0.5.1

A fast TUI for Git with staging, conflicts, rebase, and palette-first UX
Documentation
use crate::commands::{Command, CommandResult, CommandError};
use crate::app::{AppState, Action, reducer};
use crate::services::GitService;
use tracing::instrument;

/// Apply a stash (without dropping)
pub struct ApplyStashCommand {
    pub name: String,
}

impl Command for ApplyStashCommand {
    #[instrument(skip(self, git, state), fields(name = %self.name))]
    fn execute(
        &self,
        git: &GitService,
        state: &AppState,
    ) -> Result<CommandResult, CommandError> {
        let mut new_state = state.clone();
        new_state = reducer(new_state, Action::SetOpStatus(Some("applying stash…".into())));
        match git.stash_apply(&state.repo_path, &self.name, false) {
            Ok(_) => {
                new_state = reducer(new_state, Action::SetFeedback(Some(format!("Applied stash {}", self.name))));
                new_state = reducer(new_state, Action::SetRefreshing(true));
                new_state = reducer(new_state, Action::AppendOpLog(format!("applied stash {}", self.name)));
            }
            Err(e) => {
                new_state = reducer(new_state, Action::AppendOpLog(format!("stash apply error: {e}")));
                new_state = reducer(new_state, Action::SetStatusError(Some(format!("stash apply error: {e}"))));
            }
        }
        new_state = reducer(new_state, Action::SetOpStatus(None));
        Ok(CommandResult::StateUpdate(new_state))
    }
}

/// Pop a stash (apply and drop)
pub struct PopStashCommand {
    pub name: String,
}

impl Command for PopStashCommand {
    #[instrument(skip(self, git, state), fields(name = %self.name))]
    fn execute(
        &self,
        git: &GitService,
        state: &AppState,
    ) -> Result<CommandResult, CommandError> {
        let mut new_state = state.clone();
        new_state = reducer(new_state, Action::SetOpStatus(Some("popping stash…".into())));
        match git.stash_apply(&state.repo_path, &self.name, true) {
            Ok(_) => {
                new_state = reducer(new_state, Action::SetFeedback(Some(format!("Popped stash {}", self.name))));
                new_state = reducer(new_state, Action::SetRefreshing(true));
                new_state = reducer(new_state, Action::AppendOpLog(format!("popped stash {}", self.name)));
            }
            Err(e) => {
                new_state = reducer(new_state, Action::AppendOpLog(format!("stash pop error: {e}")));
                new_state = reducer(new_state, Action::SetStatusError(Some(format!("stash pop error: {e}"))));
            }
        }
        new_state = reducer(new_state, Action::SetOpStatus(None));
        Ok(CommandResult::StateUpdate(new_state))
    }
}

/// Create a new stash
pub struct CreateStashCommand {
    pub message: String,
}

impl Command for CreateStashCommand {
    #[instrument(skip(self, git, state), fields(message = %self.message))]
    fn execute(
        &self,
        git: &GitService,
        state: &AppState,
    ) -> Result<CommandResult, CommandError> {
        let mut new_state = state.clone();
        new_state = reducer(new_state, Action::SetOpStatus(Some("creating stash…".into())));
        match git.stash_push(&state.repo_path, &self.message) {
            Ok(_) => {
                new_state = reducer(new_state, Action::SetFeedback(Some(format!("Created stash: {}", if self.message.is_empty() { "untitled" } else { &self.message }))));
                new_state.stash_create_mode = false;
                new_state.stash_message_input.clear();
                new_state = reducer(new_state, Action::AppendOpLog(format!("created stash: {}", if self.message.is_empty() { "untitled" } else { &self.message })));
                // Explicitly refresh stashes list after creation
                new_state = reducer(new_state, Action::RefreshStashes);
            }
            Err(e) => {
                // On error, keep stash_create_mode open so user can try again or cancel
                new_state = reducer(new_state, Action::AppendOpLog(format!("stash create error: {e}")));
                new_state = reducer(new_state, Action::SetStatusError(Some(format!("stash create error: {e}"))));
            }
        }
        new_state = reducer(new_state, Action::SetOpStatus(None));
        Ok(CommandResult::StateUpdate(new_state))
    }
}

/// Delete a stash
/// 
/// Deletes the specified stash. On success, refreshes the stashes list to reflect the deletion.
/// The stash_selected index is automatically adjusted by the reducer if it becomes out of bounds.
pub struct DeleteStashCommand {
    pub name: String,
}

impl Command for DeleteStashCommand {
    #[instrument(skip(self, git, state), fields(name = %self.name))]
    fn execute(
        &self,
        git: &GitService,
        state: &AppState,
    ) -> Result<CommandResult, CommandError> {
        let mut new_state = state.clone();
        new_state = reducer(new_state, Action::SetOpStatus(Some("deleting stash…".into())));
        match git.stash_drop(&state.repo_path, &self.name) {
            Ok(_) => {
                new_state = reducer(new_state, Action::SetFeedback(Some(format!("Deleted stash {}", self.name))));
                new_state = reducer(new_state, Action::AppendOpLog(format!("deleted stash {}", self.name)));
                // Explicitly refresh stashes list after deletion to update the UI
                // The reducer will automatically adjust stash_selected if it's out of bounds
                new_state = reducer(new_state, Action::RefreshStashes);
            }
            Err(e) => {
                new_state = reducer(new_state, Action::AppendOpLog(format!("stash drop error: {e}")));
                new_state = reducer(new_state, Action::SetStatusError(Some(format!("stash drop error: {e}"))));
            }
        }
        new_state = reducer(new_state, Action::SetOpStatus(None));
        Ok(CommandResult::StateUpdate(new_state))
    }
}