gitui/asyncgit/src/diff.rs
2021-12-05 00:35:45 +01:00

217 lines
4.0 KiB
Rust

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)]
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)]
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, A>(R, Option<A>);
#[derive(Default, Clone)]
struct LastResult<P, R> {
params: P,
result: R,
}
///
pub struct AsyncDiff {
current: Arc<Mutex<Request<u64, FileDiff>>>,
last: Arc<Mutex<Option<LastResult<DiffParams, FileDiff>>>>,
sender: Sender<AsyncGitNotification>,
pending: Arc<AtomicUsize>,
repo: RepoPath,
}
impl AsyncDiff {
///
pub fn new(
repo: RepoPath,
sender: &Sender<AsyncGitNotification>,
) -> 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<Option<(DiffParams, FileDiff)>> {
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<Option<FileDiff>> {
log::trace!("request {:?}", params);
let hash = hash(&params);
{
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<Option<LastResult<DiffParams, FileDiff>>>,
>,
arc_current: &Arc<Mutex<Request<u64, FileDiff>>>,
hash: u64,
) -> Result<bool> {
let res = match params.diff_type {
DiffType::Stage => sync::diff::get_diff(
repo_path,
&params.path,
true,
Some(params.options),
)?,
DiffType::WorkDir => sync::diff::get_diff(
repo_path,
&params.path,
false,
Some(params.options),
)?,
DiffType::Commit(id) => sync::diff::get_diff_commit(
repo_path,
id,
params.path.clone(),
)?,
DiffType::Commits(ids) => sync::diff::get_diff_commits(
repo_path,
ids,
params.path.clone(),
)?,
};
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<Option<DiffParams>> {
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(())
}
}