use super::AtomicEvent;
use crate::{bgit_error::BGitError, config::global::BGitGlobalConfig, rules::Rule};
use git2::{Repository, ResetType, build::CheckoutBuilder};
use std::path::Path;
pub(crate) struct GitRestore<'a> {
name: String,
pre_check_rules: Vec<Box<dyn Rule + Send + Sync>>,
mode: Option<RestoreMode>,
_global_config: &'a BGitGlobalConfig,
}
#[derive(Debug, Clone)]
pub enum RestoreMode {
RestoreAllUnstaged,
UnstageAll,
}
impl<'a> AtomicEvent<'a> for GitRestore<'a> {
fn new(_global_config: &'a BGitGlobalConfig) -> Self
where
Self: Sized,
{
GitRestore {
name: "git_restore".to_owned(),
pre_check_rules: vec![],
mode: None,
_global_config,
}
}
fn get_name(&self) -> &str {
&self.name
}
fn get_action_description(&self) -> &str {
"Restore files from staging area or working directory"
}
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 restore_mode = if let Some(mode) = &self.mode {
mode
} else {
return Err(self.to_bgit_error("Restore mode not specified for git restore operation"));
};
match restore_mode {
RestoreMode::RestoreAllUnstaged => self.restore_all_unstaged(),
RestoreMode::UnstageAll => self.unstage_all_files(),
}
}
}
impl<'a> GitRestore<'a> {
pub fn with_mode(mut self, mode: RestoreMode) -> Self {
self.mode = Some(mode);
self
}
fn restore_all_unstaged(&self) -> Result<bool, Box<BGitError>> {
let repo = Repository::discover(Path::new("."))
.map_err(|e| self.to_bgit_error(&format!("Failed to open repository: {e}")))?;
let mut index = repo
.index()
.map_err(|e| self.to_bgit_error(&format!("Failed to get repository index: {e}")))?;
let index_tree_oid = index
.write_tree()
.map_err(|e| self.to_bgit_error(&format!("Failed to write index tree: {e}")))?;
let index_tree = repo
.find_tree(index_tree_oid)
.map_err(|e| self.to_bgit_error(&format!("Failed to find index tree: {e}")))?;
let mut checkout_opts = CheckoutBuilder::new();
checkout_opts.force(); checkout_opts.remove_untracked(false); checkout_opts.update_index(false);
repo.checkout_tree(index_tree.as_object(), Some(&mut checkout_opts))
.map_err(|e| {
self.to_bgit_error(&format!(
"Failed to checkout index tree to working directory: {e}"
))
})?;
Ok(true)
}
fn unstage_all_files(&self) -> Result<bool, Box<BGitError>> {
let repo = Repository::discover(Path::new("."))
.map_err(|e| self.to_bgit_error(&format!("Failed to open repository: {e}")))?;
let head_commit = match repo.head() {
Ok(head) => head
.peel_to_commit()
.map_err(|e| self.to_bgit_error(&format!("Failed to get HEAD commit: {e}")))?,
Err(e) if e.code() == git2::ErrorCode::UnbornBranch => {
return Err(self.to_bgit_error("Cannot restore staged files in unborn branch (no commits exist yet). Use 'git reset' or remove files from staging manually."));
}
Err(e) => {
return Err(self.to_bgit_error(&format!("Failed to get HEAD: {e}")));
}
};
repo.reset(head_commit.as_object(), ResetType::Mixed, None)
.map_err(|e| self.to_bgit_error(&format!("Failed to unstage files: {e}")))?;
Ok(true)
}
}