use anyhow::{anyhow, bail, Result}; use arc_swap::ArcSwap; use std::{ path::{Path, PathBuf}, sync::Arc, }; #[cfg(feature = "git")] mod git; mod diff; pub use diff::{DiffHandle, Hunk}; mod status; pub use status::FileChange; #[derive(Clone)] pub struct DiffProviderRegistry { providers: Vec, } impl DiffProviderRegistry { pub fn get_diff_base(&self, file: &Path) -> Option> { self.providers .iter() .find_map(|provider| match provider.get_diff_base(file) { Ok(res) => Some(res), Err(err) => { log::debug!("{err:#?}"); log::debug!("failed to open diff base for {}", file.display()); None } }) } pub fn get_current_head_name(&self, file: &Path) -> Option>>> { self.providers .iter() .find_map(|provider| match provider.get_current_head_name(file) { Ok(res) => Some(res), Err(err) => { log::debug!("{err:#?}"); log::debug!("failed to obtain current head name for {}", file.display()); None } }) } /// Fire-and-forget changed file iteration. Runs everything in a background task. Keeps /// iteration until `on_change` returns `false`. pub fn for_each_changed_file( self, cwd: PathBuf, f: impl Fn(Result) -> bool + Send + 'static, ) { tokio::task::spawn_blocking(move || { if self .providers .iter() .find_map(|provider| provider.for_each_changed_file(&cwd, &f).ok()) .is_none() { f(Err(anyhow!("no diff provider returns success"))); } }); } } impl Default for DiffProviderRegistry { fn default() -> Self { // currently only git is supported // TODO make this configurable when more providers are added let providers = vec![ #[cfg(feature = "git")] DiffProvider::Git, ]; DiffProviderRegistry { providers } } } /// A union type that includes all types that implement [DiffProvider]. We need this type to allow /// cloning [DiffProviderRegistry] as `Clone` cannot be used in trait objects. /// /// `Copy` is simply to ensure the `clone()` call is the simplest it can be. #[derive(Copy, Clone)] pub enum DiffProvider { #[cfg(feature = "git")] Git, None, } impl DiffProvider { fn get_diff_base(&self, file: &Path) -> Result> { match self { #[cfg(feature = "git")] Self::Git => git::get_diff_base(file), Self::None => bail!("No diff support compiled in"), } } fn get_current_head_name(&self, file: &Path) -> Result>>> { match self { #[cfg(feature = "git")] Self::Git => git::get_current_head_name(file), Self::None => bail!("No diff support compiled in"), } } fn for_each_changed_file( &self, cwd: &Path, f: impl Fn(Result) -> bool, ) -> Result<()> { match self { #[cfg(feature = "git")] Self::Git => git::for_each_changed_file(cwd, f), Self::None => bail!("No diff support compiled in"), } } }