1use crate::tests::TestServer;
2use call::ActiveCall;
3use fs::{FakeFs, Fs as _};
4use gpui::{BackgroundExecutor, Context as _, TestAppContext};
5use http_client::BlockedHttpClient;
6use language::{language_settings::language_settings, LanguageRegistry};
7use node_runtime::NodeRuntime;
8use project::ProjectPath;
9use remote::SshRemoteClient;
10use remote_server::{HeadlessAppState, HeadlessProject};
11use serde_json::json;
12use std::{path::Path, sync::Arc};
13
14#[gpui::test(iterations = 10)]
15async fn test_sharing_an_ssh_remote_project(
16 cx_a: &mut TestAppContext,
17 cx_b: &mut TestAppContext,
18 server_cx: &mut TestAppContext,
19) {
20 let executor = cx_a.executor();
21 let mut server = TestServer::start(executor.clone()).await;
22 let client_a = server.create_client(cx_a, "user_a").await;
23 let client_b = server.create_client(cx_b, "user_b").await;
24 server
25 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
26 .await;
27
28 // Set up project on remote FS
29 let (opts, server_ssh) = SshRemoteClient::fake_server(cx_a, server_cx);
30 let remote_fs = FakeFs::new(server_cx.executor());
31 remote_fs
32 .insert_tree(
33 "/code",
34 json!({
35 "project1": {
36 ".zed": {
37 "settings.json": r#"{"languages":{"Rust":{"language_servers":["override-rust-analyzer"]}}}"#
38 },
39 "README.md": "# project 1",
40 "src": {
41 "lib.rs": "fn one() -> usize { 1 }"
42 }
43 },
44 "project2": {
45 "README.md": "# project 2",
46 },
47 }),
48 )
49 .await;
50
51 // User A connects to the remote project via SSH.
52 server_cx.update(HeadlessProject::init);
53 let remote_http_client = Arc::new(BlockedHttpClient);
54 let node = NodeRuntime::unavailable();
55 let languages = Arc::new(LanguageRegistry::new(server_cx.executor()));
56 let _headless_project = server_cx.new_model(|cx| {
57 client::init_settings(cx);
58 HeadlessProject::new(
59 HeadlessAppState {
60 session: server_ssh,
61 fs: remote_fs.clone(),
62 http_client: remote_http_client,
63 node_runtime: node,
64 languages,
65 },
66 cx,
67 )
68 });
69
70 let client_ssh = SshRemoteClient::fake_client(opts, cx_a).await;
71 let (project_a, worktree_id) = client_a
72 .build_ssh_project("/code/project1", client_ssh, cx_a)
73 .await;
74
75 // While the SSH worktree is being scanned, user A shares the remote project.
76 let active_call_a = cx_a.read(ActiveCall::global);
77 let project_id = active_call_a
78 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
79 .await
80 .unwrap();
81
82 // User B joins the project.
83 let project_b = client_b.join_remote_project(project_id, cx_b).await;
84 let worktree_b = project_b
85 .update(cx_b, |project, cx| project.worktree_for_id(worktree_id, cx))
86 .unwrap();
87
88 let worktree_a = project_a
89 .update(cx_a, |project, cx| project.worktree_for_id(worktree_id, cx))
90 .unwrap();
91
92 executor.run_until_parked();
93
94 worktree_a.update(cx_a, |worktree, _cx| {
95 assert_eq!(
96 worktree.paths().map(Arc::as_ref).collect::<Vec<_>>(),
97 vec![
98 Path::new(".zed"),
99 Path::new(".zed/settings.json"),
100 Path::new("README.md"),
101 Path::new("src"),
102 Path::new("src/lib.rs"),
103 ]
104 );
105 });
106
107 worktree_b.update(cx_b, |worktree, _cx| {
108 assert_eq!(
109 worktree.paths().map(Arc::as_ref).collect::<Vec<_>>(),
110 vec![
111 Path::new(".zed"),
112 Path::new(".zed/settings.json"),
113 Path::new("README.md"),
114 Path::new("src"),
115 Path::new("src/lib.rs"),
116 ]
117 );
118 });
119
120 // User B can open buffers in the remote project.
121 let buffer_b = project_b
122 .update(cx_b, |project, cx| {
123 project.open_buffer((worktree_id, "src/lib.rs"), cx)
124 })
125 .await
126 .unwrap();
127 buffer_b.update(cx_b, |buffer, cx| {
128 assert_eq!(buffer.text(), "fn one() -> usize { 1 }");
129 let ix = buffer.text().find('1').unwrap();
130 buffer.edit([(ix..ix + 1, "100")], None, cx);
131 });
132
133 executor.run_until_parked();
134
135 cx_b.read(|cx| {
136 let file = buffer_b.read(cx).file();
137 assert_eq!(
138 language_settings(Some("Rust".into()), file, cx).language_servers,
139 ["override-rust-analyzer".to_string()]
140 )
141 });
142
143 project_b
144 .update(cx_b, |project, cx| {
145 project.save_buffer_as(
146 buffer_b.clone(),
147 ProjectPath {
148 worktree_id: worktree_id.to_owned(),
149 path: Arc::from(Path::new("src/renamed.rs")),
150 },
151 cx,
152 )
153 })
154 .await
155 .unwrap();
156 assert_eq!(
157 remote_fs
158 .load("/code/project1/src/renamed.rs".as_ref())
159 .await
160 .unwrap(),
161 "fn one() -> usize { 100 }"
162 );
163 cx_b.run_until_parked();
164 cx_b.update(|cx| {
165 assert_eq!(
166 buffer_b
167 .read(cx)
168 .file()
169 .unwrap()
170 .path()
171 .to_string_lossy()
172 .to_string(),
173 "src/renamed.rs".to_string()
174 );
175 });
176}
177
178#[gpui::test]
179async fn test_ssh_collaboration_git_branches(
180 executor: BackgroundExecutor,
181 cx_a: &mut TestAppContext,
182 cx_b: &mut TestAppContext,
183 server_cx: &mut TestAppContext,
184) {
185 cx_a.set_name("a");
186 cx_b.set_name("b");
187 server_cx.set_name("server");
188
189 let mut server = TestServer::start(executor.clone()).await;
190 let client_a = server.create_client(cx_a, "user_a").await;
191 let client_b = server.create_client(cx_b, "user_b").await;
192 server
193 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
194 .await;
195
196 // Set up project on remote FS
197 let (opts, server_ssh) = SshRemoteClient::fake_server(cx_a, server_cx);
198 let remote_fs = FakeFs::new(server_cx.executor());
199 remote_fs
200 .insert_tree("/project", serde_json::json!({ ".git":{} }))
201 .await;
202
203 let branches = ["main", "dev", "feature-1"];
204 remote_fs.insert_branches(Path::new("/project/.git"), &branches);
205
206 // User A connects to the remote project via SSH.
207 server_cx.update(HeadlessProject::init);
208 let remote_http_client = Arc::new(BlockedHttpClient);
209 let node = NodeRuntime::unavailable();
210 let languages = Arc::new(LanguageRegistry::new(server_cx.executor()));
211 let headless_project = server_cx.new_model(|cx| {
212 client::init_settings(cx);
213 HeadlessProject::new(
214 HeadlessAppState {
215 session: server_ssh,
216 fs: remote_fs.clone(),
217 http_client: remote_http_client,
218 node_runtime: node,
219 languages,
220 },
221 cx,
222 )
223 });
224
225 let client_ssh = SshRemoteClient::fake_client(opts, cx_a).await;
226 let (project_a, worktree_id) = client_a
227 .build_ssh_project("/project", client_ssh, cx_a)
228 .await;
229
230 // While the SSH worktree is being scanned, user A shares the remote project.
231 let active_call_a = cx_a.read(ActiveCall::global);
232 let project_id = active_call_a
233 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
234 .await
235 .unwrap();
236
237 // User B joins the project.
238 let project_b = client_b.join_remote_project(project_id, cx_b).await;
239
240 // Give client A sometime to see that B has joined, and that the headless server
241 // has some git repositories
242 executor.run_until_parked();
243
244 let root_path = ProjectPath::root_path(worktree_id);
245
246 let branches_b = cx_b
247 .update(|cx| project_b.update(cx, |project, cx| project.branches(root_path.clone(), cx)))
248 .await
249 .unwrap();
250
251 let new_branch = branches[2];
252
253 let branches_b = branches_b
254 .into_iter()
255 .map(|branch| branch.name)
256 .collect::<Vec<_>>();
257
258 assert_eq!(&branches_b, &branches);
259
260 cx_b.update(|cx| {
261 project_b.update(cx, |project, cx| {
262 project.update_or_create_branch(root_path.clone(), new_branch.to_string(), cx)
263 })
264 })
265 .await
266 .unwrap();
267
268 executor.run_until_parked();
269
270 let server_branch = server_cx.update(|cx| {
271 headless_project.update(cx, |headless_project, cx| {
272 headless_project
273 .worktree_store
274 .update(cx, |worktree_store, cx| {
275 worktree_store
276 .current_branch(root_path.clone(), cx)
277 .unwrap()
278 })
279 })
280 });
281
282 assert_eq!(server_branch.as_ref(), branches[2]);
283
284 // Also try creating a new branch
285 cx_b.update(|cx| {
286 project_b.update(cx, |project, cx| {
287 project.update_or_create_branch(root_path.clone(), "totally-new-branch".to_string(), cx)
288 })
289 })
290 .await
291 .unwrap();
292
293 executor.run_until_parked();
294
295 let server_branch = server_cx.update(|cx| {
296 headless_project.update(cx, |headless_project, cx| {
297 headless_project
298 .worktree_store
299 .update(cx, |worktree_store, cx| {
300 worktree_store.current_branch(root_path, cx).unwrap()
301 })
302 })
303 });
304
305 assert_eq!(server_branch.as_ref(), "totally-new-branch");
306}