bgit 0.4.2

User-friendly Git wrapper for beginners, automating essential tasks like adding, committing, and pushing changes. It includes smart rules to avoid common pitfalls, such as accidentally adding sensitive files or directories and has exclusive support for portable hooks!
use super::AtomicEvent;
use crate::{bgit_error::BGitError, config::global::BGitGlobalConfig, rules::Rule};
use git2::{Repository, StashApplyOptions};
use std::path::Path;

#[derive(Debug, Clone)]
pub(crate) enum StashOperation {
    Pop,
}

pub(crate) struct GitStash<'a> {
    name: String,
    pre_check_rules: Vec<Box<dyn Rule + Send + Sync>>,
    operation: Option<StashOperation>,
    stash_index: Option<usize>,
    _global_config: &'a BGitGlobalConfig,
}

impl<'a> GitStash<'a> {
    pub fn pop_stash(_global_config: &'a BGitGlobalConfig, index: Option<usize>) -> Self {
        GitStash {
            name: "git_stash".to_owned(),
            pre_check_rules: vec![],
            operation: Some(StashOperation::Pop),
            stash_index: index,
            _global_config,
        }
    }
}

impl<'a> AtomicEvent<'a> for GitStash<'a> {
    fn new(_global_config: &'a BGitGlobalConfig) -> Self
    where
        Self: Sized,
    {
        GitStash {
            name: "git_stash".to_owned(),
            pre_check_rules: vec![],
            operation: None,
            stash_index: None,
            _global_config,
        }
    }

    fn get_name(&self) -> &str {
        &self.name
    }

    fn get_action_description(&self) -> &str {
        match &self.operation {
            Some(StashOperation::Pop) => "Pop stash and apply changes",
            None => "No stash operation defined",
        }
    }

    fn add_pre_check_rule(&mut self, rule: Box<dyn Rule + Send + Sync>) {
        self.pre_check_rules.push(rule);
    }

    fn get_pre_check_rule(&self) -> &Vec<Box<dyn Rule + Send + Sync>> {
        &self.pre_check_rules
    }

    fn raw_execute(&self) -> Result<bool, Box<BGitError>> {
        let mut repo = Repository::discover(Path::new("."))
            .map_err(|e| self.to_bgit_error(&format!("Failed to open repository: {e}")))?;

        match &self.operation {
            Some(StashOperation::Pop) => self.pop_stash_impl(&mut repo),
            None => Err(self.to_bgit_error("No stash operation defined")),
        }
    }
}

impl<'a> GitStash<'a> {
    fn pop_stash_impl(&self, repo: &mut Repository) -> Result<bool, Box<BGitError>> {
        let index = self.stash_index.unwrap_or(0);

        // Check if stash exists
        self.check_stash_exists(repo, index)?;

        let mut apply_options = StashApplyOptions::default();

        repo.stash_pop(index, Some(&mut apply_options))
            .map_err(|e| {
                self.to_bgit_error(&format!("Failed to pop stash at index {index}: {e}"))
            })?;

        Ok(true)
    }

    fn check_stash_exists(
        &self,
        repo: &mut Repository,
        index: usize,
    ) -> Result<(), Box<BGitError>> {
        let mut stash_exists = false;
        let mut callback = |stash_index: usize, _message: &str, _oid: &git2::Oid| -> bool {
            if stash_index == index {
                stash_exists = true;
                false // Stop iteration
            } else {
                true // Continue
            }
        };

        repo.stash_foreach(&mut callback)
            .map_err(|e| self.to_bgit_error(&format!("Failed to check stash existence: {e}")))?;

        if !stash_exists {
            return Err(self.to_bgit_error(&format!("Stash at index {index} does not exist")));
        }

        Ok(())
    }
}