use crate::{ error::Result, hash, sync::{self, diff::DiffOptions, CommitId, RepoPath}, AsyncGitNotification, FileDiff, }; use crossbeam_channel::Sender; use std::{ hash::Hash, sync::{ atomic::{AtomicUsize, Ordering}, Arc, Mutex, }, }; /// #[derive(Debug, Hash, Clone, PartialEq, Eq)] pub enum DiffType { /// diff two commits Commits((CommitId, CommitId)), /// diff in a given commit Commit(CommitId), /// diff against staged file Stage, /// diff against file in workdir WorkDir, } /// #[derive(Debug, Hash, Clone, PartialEq, Eq)] pub struct DiffParams { /// path to the file to diff pub path: String, /// what kind of diff pub diff_type: DiffType, /// diff options pub options: DiffOptions, } struct Request(R, Option); #[derive(Default, Clone)] struct LastResult { params: P, result: R, } /// pub struct AsyncDiff { current: Arc>>, last: Arc>>>, sender: Sender, pending: Arc, repo: RepoPath, } impl AsyncDiff { /// pub fn new( repo: RepoPath, sender: &Sender, ) -> Self { Self { repo, current: Arc::new(Mutex::new(Request(0, None))), last: Arc::new(Mutex::new(None)), sender: sender.clone(), pending: Arc::new(AtomicUsize::new(0)), } } /// pub fn last(&mut self) -> Result> { let last = self.last.lock()?; Ok(last.clone().map(|res| (res.params, res.result))) } /// pub fn refresh(&mut self) -> Result<()> { if let Ok(Some(param)) = self.get_last_param() { self.clear_current()?; self.request(param)?; } Ok(()) } /// pub fn is_pending(&self) -> bool { self.pending.load(Ordering::Relaxed) > 0 } /// pub fn request( &mut self, params: DiffParams, ) -> Result> { log::trace!("request {:?}", params); let hash = hash(¶ms); { let mut current = self.current.lock()?; if current.0 == hash { return Ok(current.1.clone()); } current.0 = hash; current.1 = None; } let arc_current = Arc::clone(&self.current); let arc_last = Arc::clone(&self.last); let sender = self.sender.clone(); let arc_pending = Arc::clone(&self.pending); let repo = self.repo.clone(); self.pending.fetch_add(1, Ordering::Relaxed); rayon_core::spawn(move || { let notify = Self::get_diff_helper( &repo, params, &arc_last, &arc_current, hash, ); let notify = match notify { Err(err) => { log::error!("get_diff_helper error: {}", err); true } Ok(notify) => notify, }; arc_pending.fetch_sub(1, Ordering::Relaxed); sender .send(if notify { AsyncGitNotification::Diff } else { AsyncGitNotification::FinishUnchanged }) .expect("error sending diff"); }); Ok(None) } fn get_diff_helper( repo_path: &RepoPath, params: DiffParams, arc_last: &Arc< Mutex>>, >, arc_current: &Arc>>, hash: u64, ) -> Result { let res = match params.diff_type { DiffType::Stage => sync::diff::get_diff( repo_path, ¶ms.path, true, Some(params.options), )?, DiffType::WorkDir => sync::diff::get_diff( repo_path, ¶ms.path, false, Some(params.options), )?, DiffType::Commit(id) => sync::diff::get_diff_commit( repo_path, id, params.path.clone(), Some(params.options), )?, DiffType::Commits(ids) => sync::diff::get_diff_commits( repo_path, ids, params.path.clone(), Some(params.options), )?, }; let mut notify = false; { let mut current = arc_current.lock()?; if current.0 == hash { current.1 = Some(res.clone()); notify = true; } } { let mut last = arc_last.lock()?; *last = Some(LastResult { result: res, params, }); } Ok(notify) } fn get_last_param(&self) -> Result> { Ok(self.last.lock()?.clone().map(|e| e.params)) } fn clear_current(&mut self) -> Result<()> { let mut current = self.current.lock()?; current.0 = 0; current.1 = None; Ok(()) } }