gnostr_asyncgit/
diff.rs

1use std::{
2    hash::Hash,
3    sync::{
4        atomic::{AtomicUsize, Ordering},
5        Arc, Mutex,
6    },
7};
8
9use crossbeam_channel::Sender;
10
11use crate::{
12    error::Result,
13    hash,
14    sync::{self, commit_files::OldNew, diff::DiffOptions, CommitId, RepoPath},
15    AsyncGitNotification, FileDiff,
16};
17
18///
19#[derive(Debug, Hash, Clone, PartialEq, Eq)]
20pub enum DiffType {
21    /// diff two commits
22    Commits(OldNew<CommitId>),
23    /// diff in a given commit
24    Commit(CommitId),
25    /// diff against staged file
26    Stage,
27    /// diff against file in workdir
28    WorkDir,
29}
30
31///
32#[derive(Debug, Hash, Clone, PartialEq, Eq)]
33pub struct DiffParams {
34    /// path to the file to diff
35    pub path: String,
36    /// what kind of diff
37    pub diff_type: DiffType,
38    /// diff options
39    pub options: DiffOptions,
40}
41
42struct Request<R, A>(R, Option<A>);
43
44#[derive(Default, Clone)]
45struct LastResult<P, R> {
46    params: P,
47    result: R,
48}
49
50///
51pub struct AsyncDiff {
52    current: Arc<Mutex<Request<u64, FileDiff>>>,
53    last: Arc<Mutex<Option<LastResult<DiffParams, FileDiff>>>>,
54    sender: Sender<AsyncGitNotification>,
55    pending: Arc<AtomicUsize>,
56    repo: RepoPath,
57}
58
59impl AsyncDiff {
60    ///
61    pub fn new(repo: RepoPath, sender: &Sender<AsyncGitNotification>) -> Self {
62        Self {
63            repo,
64            current: Arc::new(Mutex::new(Request(0, None))),
65            last: Arc::new(Mutex::new(None)),
66            sender: sender.clone(),
67            pending: Arc::new(AtomicUsize::new(0)),
68        }
69    }
70
71    ///
72    pub fn last(&mut self) -> Result<Option<(DiffParams, FileDiff)>> {
73        let last = self.last.lock()?;
74
75        Ok(last.clone().map(|res| (res.params, res.result)))
76    }
77
78    ///
79    pub fn refresh(&mut self) -> Result<()> {
80        if let Ok(Some(param)) = self.get_last_param() {
81            self.clear_current()?;
82            self.request(param)?;
83        }
84        Ok(())
85    }
86
87    ///
88    pub fn is_pending(&self) -> bool {
89        self.pending.load(Ordering::Relaxed) > 0
90    }
91
92    ///
93    pub fn request(&mut self, params: DiffParams) -> Result<Option<FileDiff>> {
94        log::trace!("request {:?}", params);
95
96        //hash
97        let hash = hash(&params);
98
99        {
100            let mut current = self.current.lock()?;
101
102            if current.0 == hash {
103                return Ok(current.1.clone());
104            }
105
106            current.0 = hash;
107            current.1 = None;
108        }
109
110        let arc_current = Arc::clone(&self.current);
111        let arc_last = Arc::clone(&self.last);
112        let sender = self.sender.clone();
113        let arc_pending = Arc::clone(&self.pending);
114        let repo = self.repo.clone();
115
116        self.pending.fetch_add(1, Ordering::Relaxed);
117
118        rayon_core::spawn(move || {
119            let notify = Self::get_diff_helper(&repo, params, &arc_last, &arc_current, hash);
120
121            let notify = match notify {
122                Err(err) => {
123                    log::error!("get_diff_helper error: {}", err);
124                    true
125                }
126                Ok(notify) => notify,
127            };
128
129            arc_pending.fetch_sub(1, Ordering::Relaxed);
130
131            sender
132                .send(if notify {
133                    AsyncGitNotification::Diff
134                } else {
135                    AsyncGitNotification::FinishUnchanged
136                })
137                .expect("error sending diff");
138        });
139
140        Ok(None)
141    }
142
143    fn get_diff_helper(
144        repo_path: &RepoPath,
145        params: DiffParams,
146        arc_last: &Arc<Mutex<Option<LastResult<DiffParams, FileDiff>>>>,
147        arc_current: &Arc<Mutex<Request<u64, FileDiff>>>,
148        hash: u64,
149    ) -> Result<bool> {
150        let res = match params.diff_type {
151            DiffType::Stage => {
152                sync::diff::get_diff(repo_path, &params.path, true, Some(params.options))?
153            }
154            DiffType::WorkDir => {
155                sync::diff::get_diff(repo_path, &params.path, false, Some(params.options))?
156            }
157            DiffType::Commit(id) => sync::diff::get_diff_commit(
158                repo_path,
159                id,
160                params.path.clone(),
161                Some(params.options),
162            )?,
163            DiffType::Commits(ids) => sync::diff::get_diff_commits(
164                repo_path,
165                ids,
166                params.path.clone(),
167                Some(params.options),
168            )?,
169        };
170
171        let mut notify = false;
172        {
173            let mut current = arc_current.lock()?;
174            if current.0 == hash {
175                current.1 = Some(res.clone());
176                notify = true;
177            }
178        }
179
180        {
181            let mut last = arc_last.lock()?;
182            *last = Some(LastResult {
183                result: res,
184                params,
185            });
186        }
187
188        Ok(notify)
189    }
190
191    fn get_last_param(&self) -> Result<Option<DiffParams>> {
192        Ok(self.last.lock()?.clone().map(|e| e.params))
193    }
194
195    fn clear_current(&mut self) -> Result<()> {
196        let mut current = self.current.lock()?;
197        current.0 = 0;
198        current.1 = None;
199        Ok(())
200    }
201}