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, _: AsyncApp) -> BoxFuture<Option<String>> {
62 let state = self.state.lock();
63 let content = state.index_contents.get(path.as_ref()).cloned();
64 async { content }.boxed()
65 }
66
67 fn load_committed_text(&self, path: RepoPath, _: AsyncApp) -> BoxFuture<Option<String>> {
68 let state = self.state.lock();
69 let content = state.head_contents.get(path.as_ref()).cloned();
70 async { content }.boxed()
71 }
72
73 fn set_index_text(
74 &self,
75 path: RepoPath,
76 content: Option<String>,
77 _env: HashMap<String, String>,
78 cx: AsyncApp,
79 ) -> BoxFuture<anyhow::Result<()>> {
80 let state = self.state.clone();
81 let executor = cx.background_executor().clone();
82 async move {
83 executor.simulate_random_delay().await;
84
85 let mut state = state.lock();
86 if let Some(message) = state.simulated_index_write_error_message.clone() {
87 return Err(anyhow::anyhow!(message));
88 }
89
90 if let Some(content) = content {
91 state.index_contents.insert(path.clone(), content);
92 } else {
93 state.index_contents.remove(&path);
94 }
95 state
96 .event_emitter
97 .try_send(state.path.clone())
98 .expect("Dropped repo change event");
99
100 Ok(())
101 }
102 .boxed()
103 }
104
105 fn remote_url(&self, _name: &str) -> Option<String> {
106 None
107 }
108
109 fn head_sha(&self) -> Option<String> {
110 None
111 }
112
113 fn merge_head_shas(&self) -> Vec<String> {
114 vec![]
115 }
116
117 fn show(&self, _: String, _: AsyncApp) -> BoxFuture<Result<CommitDetails>> {
118 unimplemented!()
119 }
120
121 fn reset(&self, _: String, _: ResetMode, _: HashMap<String, String>) -> BoxFuture<Result<()>> {
122 unimplemented!()
123 }
124
125 fn checkout_files(
126 &self,
127 _: String,
128 _: Vec<RepoPath>,
129 _: HashMap<String, String>,
130 ) -> BoxFuture<Result<()>> {
131 unimplemented!()
132 }
133
134 fn path(&self) -> PathBuf {
135 let state = self.state.lock();
136 state.path.clone()
137 }
138
139 fn main_repository_path(&self) -> PathBuf {
140 self.path()
141 }
142
143 fn status(&self, path_prefixes: &[RepoPath]) -> Result<GitStatus> {
144 let state = self.state.lock();
145
146 let mut entries = state
147 .statuses
148 .iter()
149 .filter_map(|(repo_path, status)| {
150 if path_prefixes
151 .iter()
152 .any(|path_prefix| repo_path.0.starts_with(path_prefix))
153 {
154 Some((repo_path.to_owned(), *status))
155 } else {
156 None
157 }
158 })
159 .collect::<Vec<_>>();
160 entries.sort_unstable_by(|(a, _), (b, _)| a.cmp(&b));
161
162 Ok(GitStatus {
163 entries: entries.into(),
164 })
165 }
166
167 fn branches(&self) -> BoxFuture<Result<Vec<Branch>>> {
168 let state = self.state.lock();
169 let current_branch = &state.current_branch_name;
170 let result = Ok(state
171 .branches
172 .iter()
173 .map(|branch_name| Branch {
174 is_head: Some(branch_name) == current_branch.as_ref(),
175 name: branch_name.into(),
176 most_recent_commit: None,
177 upstream: None,
178 })
179 .collect());
180
181 async { result }.boxed()
182 }
183
184 fn change_branch(&self, name: String, _: AsyncApp) -> BoxFuture<Result<()>> {
185 let mut state = self.state.lock();
186 state.current_branch_name = Some(name.to_owned());
187 state
188 .event_emitter
189 .try_send(state.path.clone())
190 .expect("Dropped repo change event");
191 async { Ok(()) }.boxed()
192 }
193
194 fn create_branch(&self, name: String, _: AsyncApp) -> BoxFuture<Result<()>> {
195 let mut state = self.state.lock();
196 state.branches.insert(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 blame(
205 &self,
206 path: RepoPath,
207 _content: Rope,
208 _cx: AsyncApp,
209 ) -> BoxFuture<Result<crate::blame::Blame>> {
210 let state = self.state.lock();
211 let result = state
212 .blames
213 .get(&path)
214 .with_context(|| format!("failed to get blame for {:?}", path.0))
215 .cloned();
216 async { result }.boxed()
217 }
218
219 fn stage_paths(
220 &self,
221 _paths: Vec<RepoPath>,
222 _env: HashMap<String, String>,
223 _cx: AsyncApp,
224 ) -> BoxFuture<Result<()>> {
225 unimplemented!()
226 }
227
228 fn unstage_paths(
229 &self,
230 _paths: Vec<RepoPath>,
231 _env: HashMap<String, String>,
232 _cx: AsyncApp,
233 ) -> BoxFuture<Result<()>> {
234 unimplemented!()
235 }
236
237 fn commit(
238 &self,
239 _message: SharedString,
240 _name_and_email: Option<(SharedString, SharedString)>,
241 _env: HashMap<String, String>,
242 _: AsyncApp,
243 ) -> BoxFuture<Result<()>> {
244 unimplemented!()
245 }
246
247 fn push(
248 &self,
249 _branch: String,
250 _remote: String,
251 _options: Option<PushOptions>,
252 _ask_pass: AskPassSession,
253 _env: HashMap<String, String>,
254 _cx: AsyncApp,
255 ) -> BoxFuture<Result<RemoteCommandOutput>> {
256 unimplemented!()
257 }
258
259 fn pull(
260 &self,
261 _branch: String,
262 _remote: String,
263 _ask_pass: AskPassSession,
264 _env: HashMap<String, String>,
265 _cx: AsyncApp,
266 ) -> BoxFuture<Result<RemoteCommandOutput>> {
267 unimplemented!()
268 }
269
270 fn fetch(
271 &self,
272 _ask_pass: AskPassSession,
273 _env: HashMap<String, String>,
274 _cx: AsyncApp,
275 ) -> BoxFuture<Result<RemoteCommandOutput>> {
276 unimplemented!()
277 }
278
279 fn get_remotes(
280 &self,
281 _branch: Option<String>,
282 _cx: AsyncApp,
283 ) -> BoxFuture<Result<Vec<Remote>>> {
284 unimplemented!()
285 }
286
287 fn check_for_pushed_commit(&self, _cx: AsyncApp) -> BoxFuture<Result<Vec<SharedString>>> {
288 unimplemented!()
289 }
290
291 fn diff(&self, _diff: DiffType, _cx: AsyncApp) -> BoxFuture<Result<String>> {
292 unimplemented!()
293 }
294}