fake_repository.rs

  1use crate::{
  2    blame::Blame,
  3    repository::{
  4        Branch, CommitDetails, DiffType, GitRepository, PushOptions, Remote, RemoteCommandOutput,
  5        RepoPath, ResetMode,
  6    },
  7    status::{FileStatus, GitStatus},
  8};
  9use anyhow::{Context, Result};
 10use askpass::AskPassSession;
 11use collections::{HashMap, HashSet};
 12use futures::{future::BoxFuture, FutureExt as _};
 13use gpui::{AsyncApp, SharedString};
 14use parking_lot::Mutex;
 15use rope::Rope;
 16use std::{path::PathBuf, sync::Arc};
 17
 18#[derive(Debug, Clone)]
 19pub struct FakeGitRepository {
 20    state: Arc<Mutex<FakeGitRepositoryState>>,
 21}
 22
 23#[derive(Debug, Clone)]
 24pub struct FakeGitRepositoryState {
 25    pub path: PathBuf,
 26    pub event_emitter: smol::channel::Sender<PathBuf>,
 27    pub head_contents: HashMap<RepoPath, String>,
 28    pub index_contents: HashMap<RepoPath, String>,
 29    pub blames: HashMap<RepoPath, Blame>,
 30    pub statuses: HashMap<RepoPath, FileStatus>,
 31    pub current_branch_name: Option<String>,
 32    pub branches: HashSet<String>,
 33    pub simulated_index_write_error_message: Option<String>,
 34}
 35
 36impl FakeGitRepository {
 37    pub fn open(state: Arc<Mutex<FakeGitRepositoryState>>) -> Arc<dyn GitRepository> {
 38        Arc::new(FakeGitRepository { state })
 39    }
 40}
 41
 42impl FakeGitRepositoryState {
 43    pub fn new(path: PathBuf, event_emitter: smol::channel::Sender<PathBuf>) -> Self {
 44        FakeGitRepositoryState {
 45            path,
 46            event_emitter,
 47            head_contents: Default::default(),
 48            index_contents: Default::default(),
 49            blames: Default::default(),
 50            statuses: Default::default(),
 51            current_branch_name: Default::default(),
 52            branches: Default::default(),
 53            simulated_index_write_error_message: None,
 54        }
 55    }
 56}
 57
 58impl GitRepository for FakeGitRepository {
 59    fn reload_index(&self) {}
 60
 61    fn load_index_text(&self, path: RepoPath, cx: AsyncApp) -> BoxFuture<Option<String>> {
 62        let state = self.state.lock();
 63        let content = state.index_contents.get(path.as_ref()).cloned();
 64        let executor = cx.background_executor().clone();
 65        async move {
 66            executor.simulate_random_delay().await;
 67            content
 68        }
 69        .boxed()
 70    }
 71
 72    fn load_committed_text(&self, path: RepoPath, cx: AsyncApp) -> BoxFuture<Option<String>> {
 73        let state = self.state.lock();
 74        let content = state.head_contents.get(path.as_ref()).cloned();
 75        let executor = cx.background_executor().clone();
 76        async move {
 77            executor.simulate_random_delay().await;
 78            content
 79        }
 80        .boxed()
 81    }
 82
 83    fn set_index_text(
 84        &self,
 85        path: RepoPath,
 86        content: Option<String>,
 87        _env: HashMap<String, String>,
 88        cx: AsyncApp,
 89    ) -> BoxFuture<anyhow::Result<()>> {
 90        let state = self.state.clone();
 91        let executor = cx.background_executor().clone();
 92        async move {
 93            executor.simulate_random_delay().await;
 94
 95            let mut state = state.lock();
 96            if let Some(message) = state.simulated_index_write_error_message.clone() {
 97                return Err(anyhow::anyhow!(message));
 98            }
 99
100            if let Some(content) = content {
101                state.index_contents.insert(path.clone(), content);
102            } else {
103                state.index_contents.remove(&path);
104            }
105            state
106                .event_emitter
107                .try_send(state.path.clone())
108                .expect("Dropped repo change event");
109
110            Ok(())
111        }
112        .boxed()
113    }
114
115    fn remote_url(&self, _name: &str) -> Option<String> {
116        None
117    }
118
119    fn head_sha(&self) -> Option<String> {
120        None
121    }
122
123    fn merge_head_shas(&self) -> Vec<String> {
124        vec![]
125    }
126
127    fn show(&self, _: String, _: AsyncApp) -> BoxFuture<Result<CommitDetails>> {
128        unimplemented!()
129    }
130
131    fn reset(&self, _: String, _: ResetMode, _: HashMap<String, String>) -> BoxFuture<Result<()>> {
132        unimplemented!()
133    }
134
135    fn checkout_files(
136        &self,
137        _: String,
138        _: Vec<RepoPath>,
139        _: HashMap<String, String>,
140    ) -> BoxFuture<Result<()>> {
141        unimplemented!()
142    }
143
144    fn path(&self) -> PathBuf {
145        let state = self.state.lock();
146        state.path.clone()
147    }
148
149    fn main_repository_path(&self) -> PathBuf {
150        self.path()
151    }
152
153    fn status(&self, path_prefixes: &[RepoPath]) -> Result<GitStatus> {
154        let state = self.state.lock();
155
156        let mut entries = state
157            .statuses
158            .iter()
159            .filter_map(|(repo_path, status)| {
160                if path_prefixes
161                    .iter()
162                    .any(|path_prefix| repo_path.0.starts_with(path_prefix))
163                {
164                    Some((repo_path.to_owned(), *status))
165                } else {
166                    None
167                }
168            })
169            .collect::<Vec<_>>();
170        entries.sort_unstable_by(|(a, _), (b, _)| a.cmp(&b));
171
172        Ok(GitStatus {
173            entries: entries.into(),
174        })
175    }
176
177    fn branches(&self) -> BoxFuture<Result<Vec<Branch>>> {
178        let state = self.state.lock();
179        let current_branch = &state.current_branch_name;
180        let result = Ok(state
181            .branches
182            .iter()
183            .map(|branch_name| Branch {
184                is_head: Some(branch_name) == current_branch.as_ref(),
185                name: branch_name.into(),
186                most_recent_commit: None,
187                upstream: None,
188            })
189            .collect());
190
191        async { result }.boxed()
192    }
193
194    fn change_branch(&self, name: String, _: AsyncApp) -> BoxFuture<Result<()>> {
195        let mut state = self.state.lock();
196        state.current_branch_name = Some(name.to_owned());
197        state
198            .event_emitter
199            .try_send(state.path.clone())
200            .expect("Dropped repo change event");
201        async { Ok(()) }.boxed()
202    }
203
204    fn create_branch(&self, name: String, _: AsyncApp) -> BoxFuture<Result<()>> {
205        let mut state = self.state.lock();
206        state.branches.insert(name.to_owned());
207        state
208            .event_emitter
209            .try_send(state.path.clone())
210            .expect("Dropped repo change event");
211        async { Ok(()) }.boxed()
212    }
213
214    fn blame(
215        &self,
216        path: RepoPath,
217        _content: Rope,
218        _cx: AsyncApp,
219    ) -> BoxFuture<Result<crate::blame::Blame>> {
220        let state = self.state.lock();
221        let result = state
222            .blames
223            .get(&path)
224            .with_context(|| format!("failed to get blame for {:?}", path.0))
225            .cloned();
226        async { result }.boxed()
227    }
228
229    fn stage_paths(
230        &self,
231        _paths: Vec<RepoPath>,
232        _env: HashMap<String, String>,
233        _cx: AsyncApp,
234    ) -> BoxFuture<Result<()>> {
235        unimplemented!()
236    }
237
238    fn unstage_paths(
239        &self,
240        _paths: Vec<RepoPath>,
241        _env: HashMap<String, String>,
242        _cx: AsyncApp,
243    ) -> BoxFuture<Result<()>> {
244        unimplemented!()
245    }
246
247    fn commit(
248        &self,
249        _message: SharedString,
250        _name_and_email: Option<(SharedString, SharedString)>,
251        _env: HashMap<String, String>,
252        _: AsyncApp,
253    ) -> BoxFuture<Result<()>> {
254        unimplemented!()
255    }
256
257    fn push(
258        &self,
259        _branch: String,
260        _remote: String,
261        _options: Option<PushOptions>,
262        _ask_pass: AskPassSession,
263        _env: HashMap<String, String>,
264        _cx: AsyncApp,
265    ) -> BoxFuture<Result<RemoteCommandOutput>> {
266        unimplemented!()
267    }
268
269    fn pull(
270        &self,
271        _branch: String,
272        _remote: String,
273        _ask_pass: AskPassSession,
274        _env: HashMap<String, String>,
275        _cx: AsyncApp,
276    ) -> BoxFuture<Result<RemoteCommandOutput>> {
277        unimplemented!()
278    }
279
280    fn fetch(
281        &self,
282        _ask_pass: AskPassSession,
283        _env: HashMap<String, String>,
284        _cx: AsyncApp,
285    ) -> BoxFuture<Result<RemoteCommandOutput>> {
286        unimplemented!()
287    }
288
289    fn get_remotes(
290        &self,
291        _branch: Option<String>,
292        _cx: AsyncApp,
293    ) -> BoxFuture<Result<Vec<Remote>>> {
294        unimplemented!()
295    }
296
297    fn check_for_pushed_commit(&self, _cx: AsyncApp) -> BoxFuture<Result<Vec<SharedString>>> {
298        unimplemented!()
299    }
300
301    fn diff(&self, _diff: DiffType, _cx: AsyncApp) -> BoxFuture<Result<String>> {
302        unimplemented!()
303    }
304}