SSH Remoting: Fix bugs in worktree syncing (#18406)

Release Notes:

- N/A

---------

Co-authored-by: conrad <conrad@zed.dev>
This commit is contained in:
Mikayla Maki 2024-09-26 12:03:57 -07:00 committed by GitHub
parent 11058765be
commit 71da81c743
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 157 additions and 92 deletions

View file

@ -32,6 +32,7 @@ macro_rules! id_type {
#[allow(unused)] #[allow(unused)]
#[allow(missing_docs)] #[allow(missing_docs)]
pub fn from_proto(value: u64) -> Self { pub fn from_proto(value: u64) -> Self {
debug_assert!(value != 0);
Self(value as i32) Self(value as i32)
} }

View file

@ -285,7 +285,7 @@ impl Database {
) )
.one(&*tx) .one(&*tx)
.await? .await?
.ok_or_else(|| anyhow!("no such project"))?; .ok_or_else(|| anyhow!("no such project: {project_id}"))?;
// Update metadata. // Update metadata.
worktree::Entity::update(worktree::ActiveModel { worktree::Entity::update(worktree::ActiveModel {

View file

@ -246,7 +246,7 @@ async fn test_channel_notes_participant_indices(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await .await
.unwrap(); .unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
// Clients A and B open the same file. // Clients A and B open the same file.

View file

@ -76,7 +76,7 @@ async fn test_host_disconnect(
.await .await
.unwrap(); .unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
cx_a.background_executor.run_until_parked(); cx_a.background_executor.run_until_parked();
assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer())); assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
@ -192,7 +192,7 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
.await .await
.unwrap(); .unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Open a buffer as client A // Open a buffer as client A
let buffer_a = project_a let buffer_a = project_a
@ -308,7 +308,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await .await
.unwrap(); .unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Open a file in an editor as the guest. // Open a file in an editor as the guest.
let buffer_b = project_b let buffer_b = project_b
@ -565,7 +565,7 @@ async fn test_collaborating_with_code_actions(
.unwrap(); .unwrap();
// Join the project as client B. // Join the project as client B.
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
let editor_b = workspace_b let editor_b = workspace_b
.update(cx_b, |workspace, cx| { .update(cx_b, |workspace, cx| {
@ -780,7 +780,7 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await .await
.unwrap(); .unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
let editor_b = workspace_b let editor_b = workspace_b
@ -1030,7 +1030,7 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
.await .await
.unwrap(); .unwrap();
executor.run_until_parked(); executor.run_until_parked();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
project_b.read_with(cx_b, |project, cx| { project_b.read_with(cx_b, |project, cx| {
let status = project.language_server_statuses(cx).next().unwrap().1; let status = project.language_server_statuses(cx).next().unwrap().1;
@ -1126,9 +1126,7 @@ async fn test_share_project(
.await .await
.unwrap(); .unwrap();
let client_b_peer_id = client_b.peer_id().unwrap(); let client_b_peer_id = client_b.peer_id().unwrap();
let project_b = client_b let project_b = client_b.join_remote_project(initial_project.id, cx_b).await;
.build_dev_server_project(initial_project.id, cx_b)
.await;
let replica_id_b = project_b.read_with(cx_b, |project, _| project.replica_id()); let replica_id_b = project_b.read_with(cx_b, |project, _| project.replica_id());
@ -1230,9 +1228,7 @@ async fn test_share_project(
.update(cx_c, |call, cx| call.accept_incoming(cx)) .update(cx_c, |call, cx| call.accept_incoming(cx))
.await .await
.unwrap(); .unwrap();
let _project_c = client_c let _project_c = client_c.join_remote_project(initial_project.id, cx_c).await;
.build_dev_server_project(initial_project.id, cx_c)
.await;
// Client B closes the editor, and client A sees client B's selections removed. // Client B closes the editor, and client A sees client B's selections removed.
cx_b.update(move |_| drop(editor_b)); cx_b.update(move |_| drop(editor_b));
@ -1291,7 +1287,7 @@ async fn test_on_input_format_from_host_to_guest(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await .await
.unwrap(); .unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Open a file in an editor as the host. // Open a file in an editor as the host.
let buffer_a = project_a let buffer_a = project_a
@ -1411,7 +1407,7 @@ async fn test_on_input_format_from_guest_to_host(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await .await
.unwrap(); .unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Open a file in an editor as the guest. // Open a file in an editor as the guest.
let buffer_b = project_b let buffer_b = project_b
@ -1574,7 +1570,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
.unwrap(); .unwrap();
// Client B joins the project // Client B joins the project
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
active_call_b active_call_b
.update(cx_b, |call, cx| call.set_location(Some(&project_b), cx)) .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
.await .await
@ -1836,7 +1832,7 @@ async fn test_inlay_hint_refresh_is_forwarded(
.await .await
.unwrap(); .unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
active_call_b active_call_b
.update(cx_b, |call, cx| call.set_location(Some(&project_b), cx)) .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
.await .await
@ -2050,7 +2046,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
.unwrap(); .unwrap();
// Join the project as client B. // Join the project as client B.
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
let editor_b = workspace_b let editor_b = workspace_b
.update(cx_b, |workspace, cx| { .update(cx_b, |workspace, cx| {

View file

@ -74,7 +74,7 @@ async fn test_basic_following(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await .await
.unwrap(); .unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
active_call_b active_call_b
.update(cx_b, |call, cx| call.set_location(Some(&project_b), cx)) .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
.await .await
@ -162,7 +162,7 @@ async fn test_basic_following(
executor.run_until_parked(); executor.run_until_parked();
let active_call_c = cx_c.read(ActiveCall::global); let active_call_c = cx_c.read(ActiveCall::global);
let project_c = client_c.build_dev_server_project(project_id, cx_c).await; let project_c = client_c.join_remote_project(project_id, cx_c).await;
let (workspace_c, cx_c) = client_c.build_workspace(&project_c, cx_c); let (workspace_c, cx_c) = client_c.build_workspace(&project_c, cx_c);
active_call_c active_call_c
.update(cx_c, |call, cx| call.set_location(Some(&project_c), cx)) .update(cx_c, |call, cx| call.set_location(Some(&project_c), cx))
@ -175,7 +175,7 @@ async fn test_basic_following(
cx_d.executor().run_until_parked(); cx_d.executor().run_until_parked();
let active_call_d = cx_d.read(ActiveCall::global); let active_call_d = cx_d.read(ActiveCall::global);
let project_d = client_d.build_dev_server_project(project_id, cx_d).await; let project_d = client_d.join_remote_project(project_id, cx_d).await;
let (workspace_d, cx_d) = client_d.build_workspace(&project_d, cx_d); let (workspace_d, cx_d) = client_d.build_workspace(&project_d, cx_d);
active_call_d active_call_d
.update(cx_d, |call, cx| call.set_location(Some(&project_d), cx)) .update(cx_d, |call, cx| call.set_location(Some(&project_d), cx))
@ -569,7 +569,7 @@ async fn test_following_tab_order(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await .await
.unwrap(); .unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
active_call_b active_call_b
.update(cx_b, |call, cx| call.set_location(Some(&project_b), cx)) .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
.await .await
@ -686,7 +686,7 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
.unwrap(); .unwrap();
// Client B joins the project. // Client B joins the project.
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
active_call_b active_call_b
.update(cx_b, |call, cx| call.set_location(Some(&project_b), cx)) .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
.await .await
@ -1199,7 +1199,7 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await .await
.unwrap(); .unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
active_call_b active_call_b
.update(cx_b, |call, cx| call.set_location(Some(&project_b), cx)) .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
.await .await
@ -1335,7 +1335,7 @@ async fn test_peers_simultaneously_following_each_other(
.await .await
.unwrap(); .unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
executor.run_until_parked(); executor.run_until_parked();
@ -1685,7 +1685,7 @@ async fn test_following_into_excluded_file(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await .await
.unwrap(); .unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
active_call_b active_call_b
.update(cx_b, |call, cx| call.set_location(Some(&project_b), cx)) .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
.await .await

View file

@ -1372,7 +1372,7 @@ async fn test_unshare_project(
.unwrap(); .unwrap();
let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap()); let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
executor.run_until_parked(); executor.run_until_parked();
assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer())); assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
@ -1392,7 +1392,7 @@ async fn test_unshare_project(
assert!(project_b.read_with(cx_b, |project, _| project.is_disconnected())); assert!(project_b.read_with(cx_b, |project, _| project.is_disconnected()));
// Client C opens the project. // Client C opens the project.
let project_c = client_c.build_dev_server_project(project_id, cx_c).await; let project_c = client_c.join_remote_project(project_id, cx_c).await;
// When client A unshares the project, client C's project becomes read-only. // When client A unshares the project, client C's project becomes read-only.
project_a project_a
@ -1409,7 +1409,7 @@ async fn test_unshare_project(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await .await
.unwrap(); .unwrap();
let project_c2 = client_c.build_dev_server_project(project_id, cx_c).await; let project_c2 = client_c.join_remote_project(project_id, cx_c).await;
executor.run_until_parked(); executor.run_until_parked();
assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer())); assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
@ -1514,9 +1514,9 @@ async fn test_project_reconnect(
.await .await
.unwrap(); .unwrap();
let project_b1 = client_b.build_dev_server_project(project1_id, cx_b).await; let project_b1 = client_b.join_remote_project(project1_id, cx_b).await;
let project_b2 = client_b.build_dev_server_project(project2_id, cx_b).await; let project_b2 = client_b.join_remote_project(project2_id, cx_b).await;
let project_b3 = client_b.build_dev_server_project(project3_id, cx_b).await; let project_b3 = client_b.join_remote_project(project3_id, cx_b).await;
executor.run_until_parked(); executor.run_until_parked();
let worktree1_id = worktree_a1.read_with(cx_a, |worktree, _| { let worktree1_id = worktree_a1.read_with(cx_a, |worktree, _| {
@ -2310,8 +2310,8 @@ async fn test_propagate_saves_and_fs_changes(
.unwrap(); .unwrap();
// Join that worktree as clients B and C. // Join that worktree as clients B and C.
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
let project_c = client_c.build_dev_server_project(project_id, cx_c).await; let project_c = client_c.join_remote_project(project_id, cx_c).await;
let worktree_b = project_b.read_with(cx_b, |p, cx| p.worktrees(cx).next().unwrap()); let worktree_b = project_b.read_with(cx_b, |p, cx| p.worktrees(cx).next().unwrap());
@ -2535,7 +2535,7 @@ async fn test_git_diff_base_change(
.await .await
.unwrap(); .unwrap();
let project_remote = client_b.build_dev_server_project(project_id, cx_b).await; let project_remote = client_b.join_remote_project(project_id, cx_b).await;
let diff_base = " let diff_base = "
one one
@ -2791,7 +2791,7 @@ async fn test_git_branch_name(
.await .await
.unwrap(); .unwrap();
let project_remote = client_b.build_dev_server_project(project_id, cx_b).await; let project_remote = client_b.join_remote_project(project_id, cx_b).await;
client_a client_a
.fs() .fs()
.set_branch_name(Path::new("/dir/.git"), Some("branch-1")); .set_branch_name(Path::new("/dir/.git"), Some("branch-1"));
@ -2836,7 +2836,7 @@ async fn test_git_branch_name(
assert_branch(Some("branch-2"), project, cx) assert_branch(Some("branch-2"), project, cx)
}); });
let project_remote_c = client_c.build_dev_server_project(project_id, cx_c).await; let project_remote_c = client_c.join_remote_project(project_id, cx_c).await;
executor.run_until_parked(); executor.run_until_parked();
project_remote_c.read_with(cx_c, |project, cx| { project_remote_c.read_with(cx_c, |project, cx| {
@ -2891,7 +2891,7 @@ async fn test_git_status_sync(
.await .await
.unwrap(); .unwrap();
let project_remote = client_b.build_dev_server_project(project_id, cx_b).await; let project_remote = client_b.join_remote_project(project_id, cx_b).await;
// Wait for it to catch up to the new status // Wait for it to catch up to the new status
executor.run_until_parked(); executor.run_until_parked();
@ -2967,7 +2967,7 @@ async fn test_git_status_sync(
}); });
// And synchronization while joining // And synchronization while joining
let project_remote_c = client_c.build_dev_server_project(project_id, cx_c).await; let project_remote_c = client_c.join_remote_project(project_id, cx_c).await;
executor.run_until_parked(); executor.run_until_parked();
project_remote_c.read_with(cx_c, |project, cx| { project_remote_c.read_with(cx_c, |project, cx| {
@ -3015,7 +3015,7 @@ async fn test_fs_operations(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await .await
.unwrap(); .unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap()); let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
let worktree_b = project_b.read_with(cx_b, |project, cx| project.worktrees(cx).next().unwrap()); let worktree_b = project_b.read_with(cx_b, |project, cx| project.worktrees(cx).next().unwrap());
@ -3316,7 +3316,7 @@ async fn test_local_settings(
executor.run_until_parked(); executor.run_until_parked();
// As client B, join that project and observe the local settings. // As client B, join that project and observe the local settings.
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
let worktree_b = project_b.read_with(cx_b, |project, cx| project.worktrees(cx).next().unwrap()); let worktree_b = project_b.read_with(cx_b, |project, cx| project.worktrees(cx).next().unwrap());
executor.run_until_parked(); executor.run_until_parked();
@ -3439,7 +3439,7 @@ async fn test_buffer_conflict_after_save(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await .await
.unwrap(); .unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Open a buffer as client B // Open a buffer as client B
let buffer_b = project_b let buffer_b = project_b
@ -3503,7 +3503,7 @@ async fn test_buffer_reloading(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await .await
.unwrap(); .unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Open a buffer as client B // Open a buffer as client B
let buffer_b = project_b let buffer_b = project_b
@ -3557,7 +3557,7 @@ async fn test_editing_while_guest_opens_buffer(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await .await
.unwrap(); .unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Open a buffer as client A // Open a buffer as client A
let buffer_a = project_a let buffer_a = project_a
@ -3605,7 +3605,7 @@ async fn test_leaving_worktree_while_opening_buffer(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await .await
.unwrap(); .unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
// See that a guest has joined as client A. // See that a guest has joined as client A.
executor.run_until_parked(); executor.run_until_parked();
@ -3652,7 +3652,7 @@ async fn test_canceling_buffer_opening(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await .await
.unwrap(); .unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
let buffer_a = project_a let buffer_a = project_a
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
@ -3709,8 +3709,8 @@ async fn test_leaving_project(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await .await
.unwrap(); .unwrap();
let project_b1 = client_b.build_dev_server_project(project_id, cx_b).await; let project_b1 = client_b.join_remote_project(project_id, cx_b).await;
let project_c = client_c.build_dev_server_project(project_id, cx_c).await; let project_c = client_c.join_remote_project(project_id, cx_c).await;
// Client A sees that a guest has joined. // Client A sees that a guest has joined.
executor.run_until_parked(); executor.run_until_parked();
@ -3751,7 +3751,7 @@ async fn test_leaving_project(
}); });
// Client B re-joins the project and can open buffers as before. // Client B re-joins the project and can open buffers as before.
let project_b2 = client_b.build_dev_server_project(project_id, cx_b).await; let project_b2 = client_b.join_remote_project(project_id, cx_b).await;
executor.run_until_parked(); executor.run_until_parked();
project_a.read_with(cx_a, |project, _| { project_a.read_with(cx_a, |project, _| {
@ -3927,7 +3927,7 @@ async fn test_collaborating_with_diagnostics(
); );
// Join the worktree as client B. // Join the worktree as client B.
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Wait for server to see the diagnostics update. // Wait for server to see the diagnostics update.
executor.run_until_parked(); executor.run_until_parked();
@ -3952,7 +3952,7 @@ async fn test_collaborating_with_diagnostics(
}); });
// Join project as client C and observe the diagnostics. // Join project as client C and observe the diagnostics.
let project_c = client_c.build_dev_server_project(project_id, cx_c).await; let project_c = client_c.join_remote_project(project_id, cx_c).await;
executor.run_until_parked(); executor.run_until_parked();
let project_c_diagnostic_summaries = let project_c_diagnostic_summaries =
Rc::new(RefCell::new(project_c.read_with(cx_c, |project, cx| { Rc::new(RefCell::new(project_c.read_with(cx_c, |project, cx| {
@ -4160,7 +4160,7 @@ async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering(
.unwrap(); .unwrap();
// Join the project as client B and open all three files. // Join the project as client B and open all three files.
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
let guest_buffers = futures::future::try_join_all(file_names.iter().map(|file_name| { let guest_buffers = futures::future::try_join_all(file_names.iter().map(|file_name| {
project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, file_name), cx)) project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, file_name), cx))
})) }))
@ -4266,7 +4266,7 @@ async fn test_reloading_buffer_manually(
.await .await
.unwrap(); .unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)); let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap(); let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
@ -4364,7 +4364,7 @@ async fn test_formatting_buffer(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await .await
.unwrap(); .unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)); let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap(); let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
@ -4486,7 +4486,7 @@ async fn test_prettier_formatting_buffer(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await .await
.unwrap(); .unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.ts"), cx)); let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.ts"), cx));
let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap(); let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
@ -4599,7 +4599,7 @@ async fn test_definition(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await .await
.unwrap(); .unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Open the file on client B. // Open the file on client B.
let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)); let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
@ -4744,7 +4744,7 @@ async fn test_references(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await .await
.unwrap(); .unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Open the file on client B. // Open the file on client B.
let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "one.rs"), cx)); let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "one.rs"), cx));
@ -4901,7 +4901,7 @@ async fn test_project_search(
.await .await
.unwrap(); .unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Perform a search as the guest. // Perform a search as the guest.
let mut results = HashMap::default(); let mut results = HashMap::default();
@ -4991,7 +4991,7 @@ async fn test_document_highlights(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await .await
.unwrap(); .unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Open the file on client B. // Open the file on client B.
let open_b = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)); let open_b = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx));
@ -5109,7 +5109,7 @@ async fn test_lsp_hover(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await .await
.unwrap(); .unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Open the file as the guest // Open the file as the guest
let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)); let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx));
@ -5286,7 +5286,7 @@ async fn test_project_symbols(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await .await
.unwrap(); .unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Cause the language server to start. // Cause the language server to start.
let open_buffer_task = let open_buffer_task =
@ -5381,7 +5381,7 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await .await
.unwrap(); .unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
let open_buffer_task = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)); let open_buffer_task = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
let buffer_b1 = cx_b.executor().spawn(open_buffer_task).await.unwrap(); let buffer_b1 = cx_b.executor().spawn(open_buffer_task).await.unwrap();
@ -6470,7 +6470,7 @@ async fn test_context_collaboration_with_reconnect(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await .await
.unwrap(); .unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Client A sees that a guest has joined. // Client A sees that a guest has joined.
executor.run_until_parked(); executor.run_until_parked();

View file

@ -9,7 +9,7 @@ use remote_server::HeadlessProject;
use serde_json::json; use serde_json::json;
use std::{path::Path, sync::Arc}; use std::{path::Path, sync::Arc};
#[gpui::test] #[gpui::test(iterations = 10)]
async fn test_sharing_an_ssh_remote_project( async fn test_sharing_an_ssh_remote_project(
cx_a: &mut TestAppContext, cx_a: &mut TestAppContext,
cx_b: &mut TestAppContext, cx_b: &mut TestAppContext,
@ -54,9 +54,8 @@ async fn test_sharing_an_ssh_remote_project(
let (project_a, worktree_id) = client_a let (project_a, worktree_id) = client_a
.build_ssh_project("/code/project1", client_ssh, cx_a) .build_ssh_project("/code/project1", client_ssh, cx_a)
.await; .await;
executor.run_until_parked();
// User A shares the remote project. // While the SSH worktree is being scanned, user A shares the remote project.
let active_call_a = cx_a.read(ActiveCall::global); let active_call_a = cx_a.read(ActiveCall::global);
let project_id = active_call_a let project_id = active_call_a
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
@ -64,12 +63,30 @@ async fn test_sharing_an_ssh_remote_project(
.unwrap(); .unwrap();
// User B joins the project. // User B joins the project.
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
let worktree_b = project_b let worktree_b = project_b
.update(cx_b, |project, cx| project.worktree_for_id(worktree_id, cx)) .update(cx_b, |project, cx| project.worktree_for_id(worktree_id, cx))
.unwrap(); .unwrap();
let worktree_a = project_a
.update(cx_a, |project, cx| project.worktree_for_id(worktree_id, cx))
.unwrap();
executor.run_until_parked(); executor.run_until_parked();
worktree_a.update(cx_a, |worktree, _cx| {
assert_eq!(
worktree.paths().map(Arc::as_ref).collect::<Vec<_>>(),
vec![
Path::new(".zed"),
Path::new(".zed/settings.json"),
Path::new("README.md"),
Path::new("src"),
Path::new("src/lib.rs"),
]
);
});
worktree_b.update(cx_b, |worktree, _cx| { worktree_b.update(cx_b, |worktree, _cx| {
assert_eq!( assert_eq!(
worktree.paths().map(Arc::as_ref).collect::<Vec<_>>(), worktree.paths().map(Arc::as_ref).collect::<Vec<_>>(),

View file

@ -921,7 +921,7 @@ impl TestClient {
}) })
} }
pub async fn build_dev_server_project( pub async fn join_remote_project(
&self, &self,
host_project_id: u64, host_project_id: u64,
guest_cx: &mut TestAppContext, guest_cx: &mut TestAppContext,

View file

@ -204,8 +204,11 @@ impl WorktreeStore {
self.loading_worktrees.insert(path.clone(), task.shared()); self.loading_worktrees.insert(path.clone(), task.shared());
} }
let task = self.loading_worktrees.get(&path).unwrap().clone(); let task = self.loading_worktrees.get(&path).unwrap().clone();
cx.background_executor().spawn(async move { cx.spawn(|this, mut cx| async move {
match task.await { let result = task.await;
this.update(&mut cx, |this, _| this.loading_worktrees.remove(&path))
.ok();
match result {
Ok(worktree) => Ok(worktree), Ok(worktree) => Ok(worktree),
Err(err) => Err((*err).cloned()), Err(err) => Err((*err).cloned()),
} }
@ -219,7 +222,8 @@ impl WorktreeStore {
visible: bool, visible: bool,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Task<Result<Model<Worktree>, Arc<anyhow::Error>>> { ) -> Task<Result<Model<Worktree>, Arc<anyhow::Error>>> {
let mut abs_path = abs_path.as_ref().to_string_lossy().to_string(); let path_key: Arc<Path> = abs_path.as_ref().into();
let mut abs_path = path_key.clone().to_string_lossy().to_string();
// If we start with `/~` that means the ssh path was something like `ssh://user@host/~/home-dir-folder/` // If we start with `/~` that means the ssh path was something like `ssh://user@host/~/home-dir-folder/`
// in which case want to strip the leading the `/`. // in which case want to strip the leading the `/`.
// On the host-side, the `~` will get expanded. // On the host-side, the `~` will get expanded.
@ -261,8 +265,9 @@ impl WorktreeStore {
) )
})?; })?;
this.update(&mut cx, |this, cx| this.add(&worktree, cx))?; this.update(&mut cx, |this, cx| {
this.add(&worktree, cx);
})?;
Ok(worktree) Ok(worktree)
}) })
} }
@ -280,10 +285,6 @@ impl WorktreeStore {
cx.spawn(move |this, mut cx| async move { cx.spawn(move |this, mut cx| async move {
let worktree = Worktree::local(path.clone(), visible, fs, next_entry_id, &mut cx).await; let worktree = Worktree::local(path.clone(), visible, fs, next_entry_id, &mut cx).await;
this.update(&mut cx, |project, _| {
project.loading_worktrees.remove(&path);
})?;
let worktree = worktree?; let worktree = worktree?;
this.update(&mut cx, |this, cx| this.add(&worktree, cx))?; this.update(&mut cx, |this, cx| this.add(&worktree, cx))?;
@ -317,7 +318,7 @@ impl WorktreeStore {
}); });
let abs_path = abs_path.as_ref().to_path_buf(); let abs_path = abs_path.as_ref().to_path_buf();
cx.spawn(move |project, mut cx| async move { cx.spawn(move |project, cx| async move {
let (tx, rx) = futures::channel::oneshot::channel(); let (tx, rx) = futures::channel::oneshot::channel();
let tx = RefCell::new(Some(tx)); let tx = RefCell::new(Some(tx));
let Some(project) = project.upgrade() else { let Some(project) = project.upgrade() else {
@ -339,14 +340,10 @@ impl WorktreeStore {
request.await?; request.await?;
let worktree = rx.await.map_err(|e| anyhow!(e))?; let worktree = rx.await.map_err(|e| anyhow!(e))?;
drop(observer); drop(observer);
project.update(&mut cx, |project, _| {
project.loading_worktrees.remove(&path);
})?;
Ok(worktree) Ok(worktree)
}) })
} }
#[track_caller]
pub fn add(&mut self, worktree: &Model<Worktree>, cx: &mut ModelContext<Self>) { pub fn add(&mut self, worktree: &Model<Worktree>, cx: &mut ModelContext<Self>) {
let worktree_id = worktree.read(cx).id(); let worktree_id = worktree.read(cx).id();
debug_assert!(self.worktrees().all(|w| w.read(cx).id() != worktree_id)); debug_assert!(self.worktrees().all(|w| w.read(cx).id() != worktree_id));
@ -553,9 +550,12 @@ impl WorktreeStore {
let client = client.clone(); let client = client.clone();
async move { async move {
if client.is_via_collab() { if client.is_via_collab() {
client.request(update).map(|result| result.is_ok()).await client
.request(update)
.map(|result| result.log_err().is_some())
.await
} else { } else {
client.send(update).is_ok() client.send(update).log_err().is_some()
} }
} }
} }

View file

@ -564,6 +564,48 @@ async fn test_canceling_buffer_opening(cx: &mut TestAppContext, server_cx: &mut
}); });
} }
#[gpui::test]
async fn test_adding_then_removing_then_adding_worktrees(
cx: &mut TestAppContext,
server_cx: &mut TestAppContext,
) {
let (project, _headless, _fs) = init_test(cx, server_cx).await;
let (_worktree, _) = project
.update(cx, |project, cx| {
project.find_or_create_worktree("/code/project1", true, cx)
})
.await
.unwrap();
let (worktree_2, _) = project
.update(cx, |project, cx| {
project.find_or_create_worktree("/code/project2", true, cx)
})
.await
.unwrap();
let worktree_id_2 = worktree_2.read_with(cx, |tree, _| tree.id());
project.update(cx, |project, cx| project.remove_worktree(worktree_id_2, cx));
let (worktree_2, _) = project
.update(cx, |project, cx| {
project.find_or_create_worktree("/code/project2", true, cx)
})
.await
.unwrap();
cx.run_until_parked();
worktree_2.update(cx, |worktree, _cx| {
assert!(worktree.is_visible());
let entries = worktree.entries(true, 0).collect::<Vec<_>>();
assert_eq!(entries.len(), 2);
assert_eq!(
entries[1].path.to_string_lossy().to_string(),
"README.md".to_string()
)
})
}
fn init_logger() { fn init_logger() {
if std::env::var("RUST_LOG").is_ok() { if std::env::var("RUST_LOG").is_ok() {
env_logger::try_init().ok(); env_logger::try_init().ok();

View file

@ -1826,10 +1826,13 @@ impl RemoteWorktree {
let initial_update = self let initial_update = self
.snapshot .snapshot
.build_initial_update(project_id, self.id().to_proto()); .build_initial_update(project_id, self.id().to_proto());
self.updates_tx = Some(tx); self.update_observer = Some(tx);
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
let mut update = initial_update; let mut update = initial_update;
loop { loop {
// SSH projects use a special project ID of 0, and we need to
// remap it to the correct one here.
update.project_id = project_id;
if !callback(update).await { if !callback(update).await {
break; break;
} }
@ -1841,7 +1844,7 @@ impl RemoteWorktree {
} }
this.update(&mut cx, |this, _| { this.update(&mut cx, |this, _| {
let this = this.as_remote_mut().unwrap(); let this = this.as_remote_mut().unwrap();
this.updates_tx.take(); this.update_observer.take();
}) })
}) })
.detach(); .detach();

View file

@ -9,12 +9,18 @@ SUMMARY
Each instance of Zed will be signed in as a different user specified in Each instance of Zed will be signed in as a different user specified in
either \`.admins.json\` or \`.admins.default.json\`. either \`.admins.json\` or \`.admins.default.json\`.
All arguments after the initial options will be passed through to the first
instance of Zed. This can be used to test SSH remoting along with collab, like
so:
$ script/zed-local -2 ssh://your-ssh-uri-here
OPTIONS OPTIONS
--help Print this help message --help Print this help message
--release Build Zed in release mode --release Build Zed in release mode
-2, -3, -4, ... Spawn multiple Zed instances, with their windows tiled. -2, -3, -4, ... Spawn multiple Zed instances, with their windows tiled.
--top Arrange the Zed windows so they take up the top half of the screen. --top Arrange the Zed windows so they take up the top half of the screen.
--stable Use stable Zed release installed on local machine for all instances (except for the first one). --stable Use stable Zed release installed on local machine for all instances (except for the first one).
`.trim(); `.trim();
const { spawn, execSync, execFileSync } = require("child_process"); const { spawn, execSync, execFileSync } = require("child_process");