Make `Project::share` and `Project::unshare` private

Antonio Scandurra and Nathan Sobo created

This is still in-progress because randomized tests are failing.

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

crates/collab/src/rpc.rs      | 540 +++++++++++-------------------------
crates/project/src/project.rs |  77 +++-
2 files changed, 217 insertions(+), 400 deletions(-)

Detailed changes

crates/collab/src/rpc.rs 🔗

@@ -336,6 +336,7 @@ impl Server {
     }
 
     async fn sign_out(self: &mut Arc<Self>, connection_id: ConnectionId) -> Result<()> {
+        println!("Signing out {:?}", connection_id);
         self.peer.disconnect(connection_id);
         let removed_connection = self.store_mut().await.remove_connection(connection_id)?;
 
@@ -554,8 +555,13 @@ impl Server {
                     })
                 })
                 .collect::<Vec<_>>();
+
+            // Add all guests other than the requesting user's own connections as collaborators
             for (peer_conn_id, (peer_replica_id, peer_user_id)) in &project.guests {
-                if *peer_conn_id != request.sender_id {
+                if receipts_with_replica_ids
+                    .iter()
+                    .all(|(receipt, _)| receipt.sender_id != *peer_conn_id)
+                {
                     collaborators.push(proto::Collaborator {
                         peer_id: peer_conn_id.0,
                         replica_id: *peer_replica_id as u32,
@@ -563,6 +569,7 @@ impl Server {
                     });
                 }
             }
+
             for conn_id in project.connection_ids() {
                 for (receipt, replica_id) in &receipts_with_replica_ids {
                     if conn_id != receipt.sender_id {
@@ -822,6 +829,7 @@ impl Server {
         request: TypedEnvelope<proto::UpdateBuffer>,
         response: Response<proto::UpdateBuffer>,
     ) -> Result<()> {
+        println!("{:?}: update buffer", request.sender_id);
         let receiver_ids = self
             .store()
             .await
@@ -1640,7 +1648,7 @@ mod tests {
         // Connect to a server as 2 clients.
         let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
         let client_a = server.create_client(cx_a, "user_a").await;
-        let client_b = server.create_client(cx_b, "user_b").await;
+        let mut client_b = server.create_client(cx_b, "user_b").await;
         server
             .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
             .await;
@@ -1673,20 +1681,10 @@ mod tests {
         worktree_a
             .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
             .await;
-        let project_id = project_a.update(cx_a, |p, _| p.next_remote_id()).await;
-        project_a.update(cx_a, |p, cx| p.share(cx)).await.unwrap();
 
         // Join that project as client B
-        let project_b = Project::remote(
-            project_id,
-            client_b.clone(),
-            client_b.user_store.clone(),
-            lang_registry.clone(),
-            fs.clone(),
-            &mut cx_b.to_async(),
-        )
-        .await
-        .unwrap();
+        let client_b_peer_id = client_b.peer_id;
+        let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 
         let replica_id_b = project_b.read_with(cx_b, |project, _| {
             assert_eq!(
@@ -1703,7 +1701,7 @@ mod tests {
         project_a
             .condition(&cx_a, |tree, _| {
                 tree.collaborators()
-                    .get(&client_b.peer_id)
+                    .get(&client_b_peer_id)
                     .map_or(false, |collaborator| {
                         collaborator.replica_id == replica_id_b
                             && collaborator.user.github_login == "user_b"
@@ -1749,14 +1747,21 @@ mod tests {
         //     .await;
 
         // Dropping the client B's project removes client B from client A's collaborators.
-        cx_b.update(move |_| drop(project_b));
+        cx_b.update(move |_| {
+            drop(client_b.project.take());
+            drop(project_b);
+        });
         project_a
             .condition(&cx_a, |project, _| project.collaborators().is_empty())
             .await;
     }
 
     #[gpui::test(iterations = 10)]
-    async fn test_unshare_project(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
+    async fn test_unshare_project(
+        deterministic: Arc<Deterministic>,
+        cx_a: &mut TestAppContext,
+        cx_b: &mut TestAppContext,
+    ) {
         let lang_registry = Arc::new(LanguageRegistry::test());
         let fs = FakeFs::new(cx_a.background());
         cx_a.foreground().forbid_parking();
@@ -1764,7 +1769,7 @@ mod tests {
         // Connect to a server as 2 clients.
         let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
         let client_a = server.create_client(cx_a, "user_a").await;
-        let client_b = server.create_client(cx_b, "user_b").await;
+        let mut client_b = server.create_client(cx_b, "user_b").await;
         server
             .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
             .await;
@@ -1796,54 +1801,27 @@ mod tests {
         worktree_a
             .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
             .await;
-        let project_id = project_a.update(cx_a, |p, _| p.next_remote_id()).await;
         let worktree_id = worktree_a.read_with(cx_a, |tree, _| tree.id());
-        project_a.update(cx_a, |p, cx| p.share(cx)).await.unwrap();
-        assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
 
         // Join that project as client B
-        let project_b = Project::remote(
-            project_id,
-            client_b.clone(),
-            client_b.user_store.clone(),
-            lang_registry.clone(),
-            fs.clone(),
-            &mut cx_b.to_async(),
-        )
-        .await
-        .unwrap();
+        let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
+        assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
         project_b
             .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
             .await
             .unwrap();
 
-        // Unshare the project as client A
-        project_a.update(cx_a, |project, cx| project.unshare(cx));
-        project_b
-            .condition(cx_b, |project, _| project.is_read_only())
-            .await;
-        assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
+        // When client B leaves the project, it gets automatically unshared.
         cx_b.update(|_| {
+            drop(client_b.project.take());
             drop(project_b);
         });
+        deterministic.run_until_parked();
+        assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
 
-        // Share the project again and ensure guests can still join.
-        project_a
-            .update(cx_a, |project, cx| project.share(cx))
-            .await
-            .unwrap();
+        // When client B joins again, the project gets re-shared.
+        let project_b2 = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
         assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
-
-        let project_b2 = Project::remote(
-            project_id,
-            client_b.clone(),
-            client_b.user_store.clone(),
-            lang_registry.clone(),
-            fs.clone(),
-            &mut cx_b.to_async(),
-        )
-        .await
-        .unwrap();
         project_b2
             .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
             .await
@@ -1859,7 +1837,7 @@ mod tests {
         // Connect to a server as 2 clients.
         let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
         let client_a = server.create_client(cx_a, "user_a").await;
-        let client_b = server.create_client(cx_b, "user_b").await;
+        let mut client_b = server.create_client(cx_b, "user_b").await;
         server
             .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
             .await;
@@ -1891,22 +1869,11 @@ mod tests {
         worktree_a
             .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
             .await;
-        let project_id = project_a.update(cx_a, |p, _| p.next_remote_id()).await;
         let worktree_id = worktree_a.read_with(cx_a, |tree, _| tree.id());
-        project_a.update(cx_a, |p, cx| p.share(cx)).await.unwrap();
-        assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
 
         // Join that project as client B
-        let project_b = Project::remote(
-            project_id,
-            client_b.clone(),
-            client_b.user_store.clone(),
-            lang_registry.clone(),
-            fs.clone(),
-            &mut cx_b.to_async(),
-        )
-        .await
-        .unwrap();
+        let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
+        assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
         project_b
             .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
             .await
@@ -1927,26 +1894,9 @@ mod tests {
             drop(project_b);
         });
 
-        // Await reconnection
-        let project_id = project_a.update(cx_a, |p, _| p.next_remote_id()).await;
-
-        // Share the project again and ensure guests can still join.
-        project_a
-            .update(cx_a, |project, cx| project.share(cx))
-            .await
-            .unwrap();
+        // Ensure guests can still join.
+        let project_b2 = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
         assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
-
-        let project_b2 = Project::remote(
-            project_id,
-            client_b.clone(),
-            client_b.user_store.clone(),
-            lang_registry.clone(),
-            fs.clone(),
-            &mut cx_b.to_async(),
-        )
-        .await
-        .unwrap();
         project_b2
             .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
             .await
@@ -1966,8 +1916,8 @@ mod tests {
         // Connect to a server as 3 clients.
         let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
         let client_a = server.create_client(cx_a, "user_a").await;
-        let client_b = server.create_client(cx_b, "user_b").await;
-        let client_c = server.create_client(cx_c, "user_c").await;
+        let mut client_b = server.create_client(cx_b, "user_b").await;
+        let mut client_c = server.create_client(cx_c, "user_c").await;
         server
             .make_contacts(vec![
                 (&client_a, cx_a),
@@ -2003,31 +1953,11 @@ mod tests {
         worktree_a
             .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
             .await;
-        let project_id = project_a.update(cx_a, |p, _| p.next_remote_id()).await;
         let worktree_id = worktree_a.read_with(cx_a, |tree, _| tree.id());
-        project_a.update(cx_a, |p, cx| p.share(cx)).await.unwrap();
 
         // Join that worktree as clients B and C.
-        let project_b = Project::remote(
-            project_id,
-            client_b.clone(),
-            client_b.user_store.clone(),
-            lang_registry.clone(),
-            fs.clone(),
-            &mut cx_b.to_async(),
-        )
-        .await
-        .unwrap();
-        let project_c = Project::remote(
-            project_id,
-            client_c.clone(),
-            client_c.user_store.clone(),
-            lang_registry.clone(),
-            fs.clone(),
-            &mut cx_c.to_async(),
-        )
-        .await
-        .unwrap();
+        let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
+        let project_c = client_c.build_remote_project(&project_a, cx_a, cx_c).await;
         let worktree_b = project_b.read_with(cx_b, |p, cx| p.worktrees(cx).next().unwrap());
         let worktree_c = project_c.read_with(cx_c, |p, cx| p.worktrees(cx).next().unwrap());
 
@@ -2166,13 +2096,7 @@ mod tests {
         .await;
 
         let (project_a, worktree_id) = client_a.build_local_project(fs, "/dir", cx_a).await;
-        let project_id = project_a.read_with(cx_a, |project, _| project.remote_id().unwrap());
-        project_a
-            .update(cx_a, |project, cx| project.share(cx))
-            .await
-            .unwrap();
-
-        let project_b = client_b.build_remote_project(project_id, cx_b).await;
+        let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 
         let worktree_a =
             project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
@@ -2319,7 +2243,7 @@ mod tests {
         // Connect to a server as 2 clients.
         let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
         let client_a = server.create_client(cx_a, "user_a").await;
-        let client_b = server.create_client(cx_b, "user_b").await;
+        let mut client_b = server.create_client(cx_b, "user_b").await;
         server
             .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
             .await;
@@ -2351,21 +2275,10 @@ mod tests {
         worktree_a
             .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
             .await;
-        let project_id = project_a.update(cx_a, |p, _| p.next_remote_id()).await;
         let worktree_id = worktree_a.read_with(cx_a, |tree, _| tree.id());
-        project_a.update(cx_a, |p, cx| p.share(cx)).await.unwrap();
 
         // Join that project as client B
-        let project_b = Project::remote(
-            project_id,
-            client_b.clone(),
-            client_b.user_store.clone(),
-            lang_registry.clone(),
-            fs.clone(),
-            &mut cx_b.to_async(),
-        )
-        .await
-        .unwrap();
+        let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 
         // Open a buffer as client B
         let buffer_b = project_b
@@ -2403,7 +2316,7 @@ mod tests {
         // Connect to a server as 2 clients.
         let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
         let client_a = server.create_client(cx_a, "user_a").await;
-        let client_b = server.create_client(cx_b, "user_b").await;
+        let mut client_b = server.create_client(cx_b, "user_b").await;
         server
             .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
             .await;
@@ -2435,21 +2348,10 @@ mod tests {
         worktree_a
             .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
             .await;
-        let project_id = project_a.update(cx_a, |p, _| p.next_remote_id()).await;
         let worktree_id = worktree_a.read_with(cx_a, |tree, _| tree.id());
-        project_a.update(cx_a, |p, cx| p.share(cx)).await.unwrap();
 
         // Join that project as client B
-        let project_b = Project::remote(
-            project_id,
-            client_b.clone(),
-            client_b.user_store.clone(),
-            lang_registry.clone(),
-            fs.clone(),
-            &mut cx_b.to_async(),
-        )
-        .await
-        .unwrap();
+        let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
         let _worktree_b = project_b.update(cx_b, |p, cx| p.worktrees(cx).next().unwrap());
 
         // Open a buffer as client B
@@ -2487,7 +2389,7 @@ mod tests {
         // Connect to a server as 2 clients.
         let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
         let client_a = server.create_client(cx_a, "user_a").await;
-        let client_b = server.create_client(cx_b, "user_b").await;
+        let mut client_b = server.create_client(cx_b, "user_b").await;
         server
             .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
             .await;
@@ -2518,21 +2420,10 @@ mod tests {
         worktree_a
             .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
             .await;
-        let project_id = project_a.update(cx_a, |p, _| p.next_remote_id()).await;
         let worktree_id = worktree_a.read_with(cx_a, |tree, _| tree.id());
-        project_a.update(cx_a, |p, cx| p.share(cx)).await.unwrap();
 
         // Join that project as client B
-        let project_b = Project::remote(
-            project_id,
-            client_b.clone(),
-            client_b.user_store.clone(),
-            lang_registry.clone(),
-            fs.clone(),
-            &mut cx_b.to_async(),
-        )
-        .await
-        .unwrap();
+        let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 
         // Open a buffer as client A
         let buffer_a = project_a
@@ -2568,7 +2459,7 @@ mod tests {
         // Connect to a server as 2 clients.
         let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
         let client_a = server.create_client(cx_a, "user_a").await;
-        let client_b = server.create_client(cx_b, "user_b").await;
+        let mut client_b = server.create_client(cx_b, "user_b").await;
         server
             .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
             .await;
@@ -2599,21 +2490,10 @@ mod tests {
         worktree_a
             .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
             .await;
-        let project_id = project_a.update(cx_a, |p, _| p.next_remote_id()).await;
         let worktree_id = worktree_a.read_with(cx_a, |tree, _| tree.id());
-        project_a.update(cx_a, |p, cx| p.share(cx)).await.unwrap();
 
         // Join that project as client B
-        let project_b = Project::remote(
-            project_id,
-            client_b.clone(),
-            client_b.user_store.clone(),
-            lang_registry.clone(),
-            fs.clone(),
-            &mut cx_b.to_async(),
-        )
-        .await
-        .unwrap();
+        let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 
         // See that a guest has joined as client A.
         project_a
@@ -2624,7 +2504,10 @@ mod tests {
         let buffer_b = cx_b
             .background()
             .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)));
-        cx_b.update(|_| drop(project_b));
+        cx_b.update(|_| {
+            drop(client_b.project.take());
+            drop(project_b);
+        });
         drop(buffer_b);
 
         // See that the guest has left.
@@ -2642,7 +2525,7 @@ mod tests {
         // Connect to a server as 2 clients.
         let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
         let client_a = server.create_client(cx_a, "user_a").await;
-        let client_b = server.create_client(cx_b, "user_b").await;
+        let mut client_b = server.create_client(cx_b, "user_b").await;
         server
             .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
             .await;
@@ -2674,25 +2557,9 @@ mod tests {
         worktree_a
             .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
             .await;
-        let project_id = project_a
-            .update(cx_a, |project, _| project.next_remote_id())
-            .await;
-        project_a
-            .update(cx_a, |project, cx| project.share(cx))
-            .await
-            .unwrap();
 
         // Join that project as client B
-        let _project_b = Project::remote(
-            project_id,
-            client_b.clone(),
-            client_b.user_store.clone(),
-            lang_registry.clone(),
-            fs.clone(),
-            &mut cx_b.to_async(),
-        )
-        .await
-        .unwrap();
+        let _project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 
         // Client A sees that a guest has joined.
         project_a
@@ -2706,16 +2573,7 @@ mod tests {
             .await;
 
         // Rejoin the project as client B
-        let _project_b = Project::remote(
-            project_id,
-            client_b.clone(),
-            client_b.user_store.clone(),
-            lang_registry.clone(),
-            fs.clone(),
-            &mut cx_b.to_async(),
-        )
-        .await
-        .unwrap();
+        let _project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 
         // Client A sees that a guest has re-joined.
         project_a
@@ -2733,10 +2591,12 @@ mod tests {
 
     #[gpui::test(iterations = 10)]
     async fn test_collaborating_with_diagnostics(
+        deterministic: Arc<Deterministic>,
         cx_a: &mut TestAppContext,
         cx_b: &mut TestAppContext,
+        cx_c: &mut TestAppContext,
     ) {
-        cx_a.foreground().forbid_parking();
+        deterministic.forbid_parking();
         let lang_registry = Arc::new(LanguageRegistry::test());
         let fs = FakeFs::new(cx_a.background());
 
@@ -2755,9 +2615,14 @@ mod tests {
         // Connect to a server as 2 clients.
         let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
         let client_a = server.create_client(cx_a, "user_a").await;
-        let client_b = server.create_client(cx_b, "user_b").await;
+        let mut client_b = server.create_client(cx_b, "user_b").await;
+        let mut client_c = server.create_client(cx_c, "user_c").await;
         server
-            .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
+            .make_contacts(vec![
+                (&client_a, cx_a),
+                (&client_b, cx_b),
+                (&client_c, cx_c),
+            ])
             .await;
 
         // Share a project as client A
@@ -2789,10 +2654,9 @@ mod tests {
             .await;
         let project_id = project_a.update(cx_a, |p, _| p.next_remote_id()).await;
         let worktree_id = worktree_a.read_with(cx_a, |tree, _| tree.id());
-        project_a.update(cx_a, |p, cx| p.share(cx)).await.unwrap();
 
         // Cause the language server to start.
-        let _ = cx_a
+        let _buffer = cx_a
             .background()
             .spawn(project_a.update(cx_a, |project, cx| {
                 project.open_buffer(
@@ -2806,6 +2670,9 @@ mod tests {
             .await
             .unwrap();
 
+        // Join the worktree as client B.
+        let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
+
         // Simulate a language server reporting errors for a file.
         let mut fake_language_server = fake_language_servers.next().await.unwrap();
         fake_language_server
@@ -2825,31 +2692,15 @@ mod tests {
         );
 
         // Wait for server to see the diagnostics update.
-        server
-            .condition(|store| {
-                let worktree = store
-                    .project(project_id)
-                    .unwrap()
-                    .worktrees
-                    .get(&worktree_id.to_proto())
-                    .unwrap();
-
-                !worktree.diagnostic_summaries.is_empty()
-            })
-            .await;
-
-        // Join the worktree as client B.
-        let project_b = Project::remote(
-            project_id,
-            client_b.clone(),
-            client_b.user_store.clone(),
-            lang_registry.clone(),
-            fs.clone(),
-            &mut cx_b.to_async(),
-        )
-        .await
-        .unwrap();
+        deterministic.run_until_parked();
+        {
+            let store = server.store.read().await;
+            let project = store.project(project_id).unwrap();
+            let worktree = project.worktrees.get(&worktree_id.to_proto()).unwrap();
+            assert!(!worktree.diagnostic_summaries.is_empty());
+        }
 
+        // Ensure client B observes the new diagnostics.
         project_b.read_with(cx_b, |project, cx| {
             assert_eq!(
                 project.diagnostic_summaries(cx).collect::<Vec<_>>(),
@@ -2867,6 +2718,25 @@ mod tests {
             )
         });
 
+        // Join project as client C and observe the diagnostics.
+        let project_c = client_c.build_remote_project(&project_a, cx_a, cx_c).await;
+        project_c.read_with(cx_c, |project, cx| {
+            assert_eq!(
+                project.diagnostic_summaries(cx).collect::<Vec<_>>(),
+                &[(
+                    ProjectPath {
+                        worktree_id,
+                        path: Arc::from(Path::new("a.rs")),
+                    },
+                    DiagnosticSummary {
+                        error_count: 1,
+                        warning_count: 0,
+                        ..Default::default()
+                    },
+                )]
+            )
+        });
+
         // Simulate a language server reporting more errors for a file.
         fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
             lsp::PublishDiagnosticsParams {
@@ -2892,23 +2762,40 @@ mod tests {
             },
         );
 
-        // Client b gets the updated summaries
-        project_b
-            .condition(&cx_b, |project, cx| {
-                project.diagnostic_summaries(cx).collect::<Vec<_>>()
-                    == &[(
-                        ProjectPath {
-                            worktree_id,
-                            path: Arc::from(Path::new("a.rs")),
-                        },
-                        DiagnosticSummary {
-                            error_count: 1,
-                            warning_count: 1,
-                            ..Default::default()
-                        },
-                    )]
-            })
-            .await;
+        // Clients B and C get the updated summaries
+        deterministic.run_until_parked();
+        project_b.read_with(cx_b, |project, cx| {
+            assert_eq!(
+                project.diagnostic_summaries(cx).collect::<Vec<_>>(),
+                [(
+                    ProjectPath {
+                        worktree_id,
+                        path: Arc::from(Path::new("a.rs")),
+                    },
+                    DiagnosticSummary {
+                        error_count: 1,
+                        warning_count: 1,
+                        ..Default::default()
+                    },
+                )]
+            );
+        });
+        project_c.read_with(cx_c, |project, cx| {
+            assert_eq!(
+                project.diagnostic_summaries(cx).collect::<Vec<_>>(),
+                [(
+                    ProjectPath {
+                        worktree_id,
+                        path: Arc::from(Path::new("a.rs")),
+                    },
+                    DiagnosticSummary {
+                        error_count: 1,
+                        warning_count: 1,
+                        ..Default::default()
+                    },
+                )]
+            );
+        });
 
         // Open the file with the errors on client B. They should be present.
         let buffer_b = cx_b
@@ -2957,16 +2844,16 @@ mod tests {
                 diagnostics: vec![],
             },
         );
-        project_a
-            .condition(cx_a, |project, cx| {
-                project.diagnostic_summaries(cx).collect::<Vec<_>>() == &[]
-            })
-            .await;
-        project_b
-            .condition(cx_b, |project, cx| {
-                project.diagnostic_summaries(cx).collect::<Vec<_>>() == &[]
-            })
-            .await;
+        deterministic.run_until_parked();
+        project_a.read_with(cx_a, |project, cx| {
+            assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
+        });
+        project_b.read_with(cx_b, |project, cx| {
+            assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
+        });
+        project_c.read_with(cx_c, |project, cx| {
+            assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
+        });
     }
 
     #[gpui::test(iterations = 10)]
@@ -3002,7 +2889,7 @@ mod tests {
         // Connect to a server as 2 clients.
         let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
         let client_a = server.create_client(cx_a, "user_a").await;
-        let client_b = server.create_client(cx_b, "user_b").await;
+        let mut client_b = server.create_client(cx_b, "user_b").await;
         server
             .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
             .await;
@@ -3034,21 +2921,10 @@ mod tests {
         worktree_a
             .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
             .await;
-        let project_id = project_a.update(cx_a, |p, _| p.next_remote_id()).await;
         let worktree_id = worktree_a.read_with(cx_a, |tree, _| tree.id());
-        project_a.update(cx_a, |p, cx| p.share(cx)).await.unwrap();
 
         // Join the worktree as client B.
-        let project_b = Project::remote(
-            project_id,
-            client_b.clone(),
-            client_b.user_store.clone(),
-            lang_registry.clone(),
-            fs.clone(),
-            &mut cx_b.to_async(),
-        )
-        .await
-        .unwrap();
+        let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 
         // Open a file in an editor as the guest.
         let buffer_b = project_b
@@ -3185,7 +3061,7 @@ mod tests {
         // Connect to a server as 2 clients.
         let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
         let client_a = server.create_client(cx_a, "user_a").await;
-        let client_b = server.create_client(cx_b, "user_b").await;
+        let mut client_b = server.create_client(cx_b, "user_b").await;
         server
             .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
             .await;
@@ -3216,25 +3092,14 @@ mod tests {
         worktree_a
             .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
             .await;
-        let project_id = project_a.update(cx_a, |p, _| p.next_remote_id()).await;
         let worktree_id = worktree_a.read_with(cx_a, |tree, _| tree.id());
-        project_a.update(cx_a, |p, cx| p.share(cx)).await.unwrap();
         let buffer_a = project_a
             .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
             .await
             .unwrap();
 
         // Join the worktree as client B.
-        let project_b = Project::remote(
-            project_id,
-            client_b.clone(),
-            client_b.user_store.clone(),
-            lang_registry.clone(),
-            fs.clone(),
-            &mut cx_b.to_async(),
-        )
-        .await
-        .unwrap();
+        let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 
         let buffer_b = cx_b
             .background()
@@ -3316,7 +3181,7 @@ mod tests {
         // Connect to a server as 2 clients.
         let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
         let client_a = server.create_client(cx_a, "user_a").await;
-        let client_b = server.create_client(cx_b, "user_b").await;
+        let mut client_b = server.create_client(cx_b, "user_b").await;
         server
             .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
             .await;
@@ -3347,21 +3212,10 @@ mod tests {
         worktree_a
             .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
             .await;
-        let project_id = project_a.update(cx_a, |p, _| p.next_remote_id()).await;
         let worktree_id = worktree_a.read_with(cx_a, |tree, _| tree.id());
-        project_a.update(cx_a, |p, cx| p.share(cx)).await.unwrap();
 
-        // Join the worktree as client B.
-        let project_b = Project::remote(
-            project_id,
-            client_b.clone(),
-            client_b.user_store.clone(),
-            lang_registry.clone(),
-            fs.clone(),
-            &mut cx_b.to_async(),
-        )
-        .await
-        .unwrap();
+        // Join the project as client B.
+        let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 
         let buffer_b = cx_b
             .background()
@@ -3430,7 +3284,7 @@ mod tests {
         // Connect to a server as 2 clients.
         let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
         let client_a = server.create_client(cx_a, "user_a").await;
-        let client_b = server.create_client(cx_b, "user_b").await;
+        let mut client_b = server.create_client(cx_b, "user_b").await;
         server
             .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
             .await;
@@ -3454,21 +3308,10 @@ mod tests {
         worktree_a
             .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
             .await;
-        let project_id = project_a.update(cx_a, |p, _| p.next_remote_id()).await;
         let worktree_id = worktree_a.read_with(cx_a, |tree, _| tree.id());
-        project_a.update(cx_a, |p, cx| p.share(cx)).await.unwrap();
 
-        // Join the worktree as client B.
-        let project_b = Project::remote(
-            project_id,
-            client_b.clone(),
-            client_b.user_store.clone(),
-            lang_registry.clone(),
-            fs.clone(),
-            &mut cx_b.to_async(),
-        )
-        .await
-        .unwrap();
+        // Join the project as client B.
+        let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 
         // Open the file on client B.
         let buffer_b = cx_b
@@ -3577,7 +3420,7 @@ mod tests {
         // Connect to a server as 2 clients.
         let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
         let client_a = server.create_client(cx_a, "user_a").await;
-        let client_b = server.create_client(cx_b, "user_b").await;
+        let mut client_b = server.create_client(cx_b, "user_b").await;
         server
             .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
             .await;
@@ -3601,21 +3444,10 @@ mod tests {
         worktree_a
             .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
             .await;
-        let project_id = project_a.update(cx_a, |p, _| p.next_remote_id()).await;
         let worktree_id = worktree_a.read_with(cx_a, |tree, _| tree.id());
-        project_a.update(cx_a, |p, cx| p.share(cx)).await.unwrap();
 
         // Join the worktree as client B.
-        let project_b = Project::remote(
-            project_id,
-            client_b.clone(),
-            client_b.user_store.clone(),
-            lang_registry.clone(),
-            fs.clone(),
-            &mut cx_b.to_async(),
-        )
-        .await
-        .unwrap();
+        let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 
         // Open the file on client B.
         let buffer_b = cx_b
@@ -3710,7 +3542,7 @@ mod tests {
         // Connect to a server as 2 clients.
         let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
         let client_a = server.create_client(cx_a, "user_a").await;
-        let client_b = server.create_client(cx_b, "user_b").await;
+        let mut client_b = server.create_client(cx_b, "user_b").await;
         server
             .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
             .await;
@@ -3725,7 +3557,6 @@ mod tests {
                 cx,
             )
         });
-        let project_id = project_a.update(cx_a, |p, _| p.next_remote_id()).await;
 
         let (worktree_1, _) = project_a
             .update(cx_a, |p, cx| {
@@ -3746,20 +3577,8 @@ mod tests {
             .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
             .await;
 
-        project_a.update(cx_a, |p, cx| p.share(cx)).await.unwrap();
-
         // Join the worktree as client B.
-        let project_b = Project::remote(
-            project_id,
-            client_b.clone(),
-            client_b.user_store.clone(),
-            lang_registry.clone(),
-            fs.clone(),
-            &mut cx_b.to_async(),
-        )
-        .await
-        .unwrap();
-
+        let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
         let results = project_b
             .update(cx_b, |project, cx| {
                 project.search(SearchQuery::text("world", false, false), cx)
@@ -3821,7 +3640,7 @@ mod tests {
         // Connect to a server as 2 clients.
         let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
         let client_a = server.create_client(cx_a, "user_a").await;
-        let client_b = server.create_client(cx_b, "user_b").await;
+        let mut client_b = server.create_client(cx_b, "user_b").await;
         server
             .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
             .await;
@@ -3845,21 +3664,10 @@ mod tests {
         worktree_a
             .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
             .await;
-        let project_id = project_a.update(cx_a, |p, _| p.next_remote_id()).await;
         let worktree_id = worktree_a.read_with(cx_a, |tree, _| tree.id());
-        project_a.update(cx_a, |p, cx| p.share(cx)).await.unwrap();
 
         // Join the worktree as client B.
-        let project_b = Project::remote(
-            project_id,
-            client_b.clone(),
-            client_b.user_store.clone(),
-            lang_registry.clone(),
-            fs.clone(),
-            &mut cx_b.to_async(),
-        )
-        .await
-        .unwrap();
+        let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 
         // Open the file on client B.
         let buffer_b = cx_b
@@ -3968,7 +3776,7 @@ mod tests {
         // Connect to a server as 2 clients.
         let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
         let client_a = server.create_client(cx_a, "user_a").await;
-        let client_b = server.create_client(cx_b, "user_b").await;
+        let mut client_b = server.create_client(cx_b, "user_b").await;
         server
             .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
             .await;
@@ -3992,21 +3800,10 @@ mod tests {
         worktree_a
             .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
             .await;
-        let project_id = project_a.update(cx_a, |p, _| p.next_remote_id()).await;
         let worktree_id = worktree_a.read_with(cx_a, |tree, _| tree.id());
-        project_a.update(cx_a, |p, cx| p.share(cx)).await.unwrap();
 
         // Join the worktree as client B.
-        let project_b = Project::remote(
-            project_id,
-            client_b.clone(),
-            client_b.user_store.clone(),
-            lang_registry.clone(),
-            fs.clone(),
-            &mut cx_b.to_async(),
-        )
-        .await
-        .unwrap();
+        let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 
         // Cause the language server to start.
         let _buffer = cx_b
@@ -4100,7 +3897,7 @@ mod tests {
         // Connect to a server as 2 clients.
         let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
         let client_a = server.create_client(cx_a, "user_a").await;
-        let client_b = server.create_client(cx_b, "user_b").await;
+        let mut client_b = server.create_client(cx_b, "user_b").await;
         server
             .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
             .await;
@@ -4125,21 +3922,10 @@ mod tests {
         worktree_a
             .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
             .await;
-        let project_id = project_a.update(cx_a, |p, _| p.next_remote_id()).await;
         let worktree_id = worktree_a.read_with(cx_a, |tree, _| tree.id());
-        project_a.update(cx_a, |p, cx| p.share(cx)).await.unwrap();
 
-        // Join the worktree as client B.
-        let project_b = Project::remote(
-            project_id,
-            client_b.clone(),
-            client_b.user_store.clone(),
-            lang_registry.clone(),
-            fs.clone(),
-            &mut cx_b.to_async(),
-        )
-        .await
-        .unwrap();
+        // Join the project as client B.
+        let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 
         let buffer_b1 = cx_b
             .background()
@@ -4200,7 +3986,7 @@ mod tests {
         // Connect to a server as 2 clients.
         let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
         let client_a = server.create_client(cx_a, "user_a").await;
-        let client_b = server.create_client(cx_b, "user_b").await;
+        let mut client_b = server.create_client(cx_b, "user_b").await;
         server
             .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
             .await;

crates/project/src/project.rs 🔗

@@ -817,11 +817,12 @@ impl Project {
         }
     }
 
-    pub fn can_share(&self, cx: &AppContext) -> bool {
-        self.is_local() && self.visible_worktrees(cx).next().is_some()
-    }
-
-    pub fn share(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
+    fn share(
+        &mut self,
+        requester_user_id: UserId,
+        requester_peer_id: PeerId,
+        cx: &mut ModelContext<Self>,
+    ) -> Task<Result<()>> {
         let project_id;
         if let ProjectClientState::Local {
             remote_id_rx,
@@ -882,7 +883,7 @@ impl Project {
         })
     }
 
-    pub fn unshare(&mut self, cx: &mut ModelContext<Self>) {
+    fn unshare(&mut self, cx: &mut ModelContext<Self>) {
         if let ProjectClientState::Local { is_shared, .. } = &mut self.client_state {
             if !*is_shared {
                 return;
@@ -929,7 +930,10 @@ impl Project {
             let client = self.client.clone();
             cx.foreground()
                 .spawn(async move {
+                    println!("SHARING because {} wanted to join!!!", requester_id);
                     share.await?;
+                    println!("DONE SHARING!!!!!");
+
                     client.send(proto::RespondToJoinProjectRequest {
                         requester_id,
                         project_id,
@@ -3521,25 +3525,36 @@ impl Project {
                         });
                         let worktree = worktree?;
 
-                        let (remote_project_id, is_shared) =
-                            project.update(&mut cx, |project, cx| {
-                                project.add_worktree(&worktree, cx);
-                                (project.remote_id(), project.is_shared())
-                            });
+                        let remote_project_id = project.update(&mut cx, |project, cx| {
+                            project.add_worktree(&worktree, cx);
+                            project.remote_id()
+                        });
 
                         if let Some(project_id) = remote_project_id {
-                            if is_shared {
-                                worktree
-                                    .update(&mut cx, |worktree, cx| {
-                                        worktree.as_local_mut().unwrap().share(project_id, cx)
-                                    })
-                                    .await?;
-                            } else {
-                                worktree
-                                    .update(&mut cx, |worktree, cx| {
-                                        worktree.as_local_mut().unwrap().register(project_id, cx)
-                                    })
-                                    .await?;
+                            // Because sharing is async, we may have *unshared* the project by the time it completes,
+                            // in which case we need to register the worktree instead.
+                            loop {
+                                if project.read_with(&cx, |project, _| project.is_shared()) {
+                                    if worktree
+                                        .update(&mut cx, |worktree, cx| {
+                                            worktree.as_local_mut().unwrap().share(project_id, cx)
+                                        })
+                                        .await
+                                        .is_ok()
+                                    {
+                                        break;
+                                    }
+                                } else {
+                                    worktree
+                                        .update(&mut cx, |worktree, cx| {
+                                            worktree
+                                                .as_local_mut()
+                                                .unwrap()
+                                                .register(project_id, cx)
+                                        })
+                                        .await?;
+                                    break;
+                                }
                             }
                         }
 
@@ -3828,7 +3843,17 @@ impl Project {
                     buffer.update(cx, |buffer, cx| buffer.remove_peer(replica_id, cx));
                 }
             }
+            println!(
+                "{} observed {:?} leaving",
+                this.user_store
+                    .read(cx)
+                    .current_user()
+                    .unwrap()
+                    .github_login,
+                peer_id
+            );
             if this.collaborators.is_empty() {
+                println!("UNSHARING!!!!");
                 this.unshare(cx);
             }
             cx.emit(Event::CollaboratorLeft(peer_id));
@@ -4087,6 +4112,7 @@ impl Project {
                 .into_iter()
                 .map(|op| language::proto::deserialize_operation(op))
                 .collect::<Result<Vec<_>, _>>()?;
+            let is_remote = this.is_remote();
             match this.opened_buffers.entry(buffer_id) {
                 hash_map::Entry::Occupied(mut e) => match e.get_mut() {
                     OpenBuffer::Strong(buffer) => {
@@ -4096,6 +4122,11 @@ impl Project {
                     OpenBuffer::Weak(_) => {}
                 },
                 hash_map::Entry::Vacant(e) => {
+                    assert!(
+                        is_remote,
+                        "received buffer update from {:?}",
+                        envelope.original_sender_id
+                    );
                     e.insert(OpenBuffer::Loading(ops));
                 }
             }