Add remote support for repository::head_sha

Anthony Eid created

Change summary

crates/collab/src/rpc.rs                     |  1 
crates/collab/tests/integration/git_tests.rs | 52 ++++++++++++++++++++++
crates/project/src/git_store.rs              | 28 +++++++++++
crates/proto/proto/git.proto                 |  9 +++
crates/proto/proto/zed.proto                 |  4 +
crates/proto/src/proto.rs                    |  4 +
6 files changed, 95 insertions(+), 3 deletions(-)

Detailed changes

crates/collab/src/rpc.rs 🔗

@@ -435,6 +435,7 @@ impl Server {
             .add_request_handler(forward_mutating_project_request::<proto::GitCreateRemote>)
             .add_request_handler(forward_mutating_project_request::<proto::GitRemoveRemote>)
             .add_request_handler(forward_read_only_project_request::<proto::GitGetWorktrees>)
+            .add_request_handler(forward_read_only_project_request::<proto::GitGetHeadSha>)
             .add_request_handler(forward_mutating_project_request::<proto::GitCreateWorktree>)
             .add_request_handler(disallow_guest_request::<proto::GitRemoveWorktree>)
             .add_request_handler(disallow_guest_request::<proto::GitRenameWorktree>)

crates/collab/tests/integration/git_tests.rs 🔗

@@ -424,6 +424,58 @@ async fn test_remote_git_worktrees(
     );
 }
 
+#[gpui::test]
+async fn test_remote_git_head_sha(
+    executor: BackgroundExecutor,
+    cx_a: &mut TestAppContext,
+    cx_b: &mut TestAppContext,
+) {
+    let mut server = TestServer::start(executor.clone()).await;
+    let client_a = server.create_client(cx_a, "user_a").await;
+    let client_b = server.create_client(cx_b, "user_b").await;
+    server
+        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+        .await;
+    let active_call_a = cx_a.read(ActiveCall::global);
+
+    client_a
+        .fs()
+        .insert_tree(
+            path!("/project"),
+            json!({ ".git": {}, "file.txt": "content" }),
+        )
+        .await;
+
+    let (project_a, _) = client_a.build_local_project(path!("/project"), cx_a).await;
+    let local_head_sha = cx_a.update(|cx| {
+        project_a
+            .read(cx)
+            .active_repository(cx)
+            .unwrap()
+            .update(cx, |repository, _| repository.head_sha())
+    });
+    let local_head_sha = local_head_sha.await.unwrap().unwrap();
+
+    let project_id = active_call_a
+        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+        .await
+        .unwrap();
+    let project_b = client_b.join_remote_project(project_id, cx_b).await;
+
+    executor.run_until_parked();
+
+    let remote_head_sha = cx_b.update(|cx| {
+        project_b
+            .read(cx)
+            .active_repository(cx)
+            .unwrap()
+            .update(cx, |repository, _| repository.head_sha())
+    });
+    let remote_head_sha = remote_head_sha.await.unwrap();
+
+    assert_eq!(remote_head_sha.unwrap(), local_head_sha);
+}
+
 #[gpui::test]
 async fn test_linked_worktrees_sync(
     executor: BackgroundExecutor,

crates/project/src/git_store.rs 🔗

@@ -594,6 +594,7 @@ impl GitStore {
         client.add_entity_request_handler(Self::handle_create_worktree);
         client.add_entity_request_handler(Self::handle_remove_worktree);
         client.add_entity_request_handler(Self::handle_rename_worktree);
+        client.add_entity_request_handler(Self::handle_get_head_sha);
     }
 
     pub fn is_local(&self) -> bool {
@@ -2466,6 +2467,21 @@ impl GitStore {
         Ok(proto::Ack {})
     }
 
+    async fn handle_get_head_sha(
+        this: Entity<Self>,
+        envelope: TypedEnvelope<proto::GitGetHeadSha>,
+        mut cx: AsyncApp,
+    ) -> Result<proto::GitGetHeadShaResponse> {
+        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
+        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
+
+        let head_sha = repository_handle
+            .update(&mut cx, |repository_handle, _| repository_handle.head_sha())
+            .await??;
+
+        Ok(proto::GitGetHeadShaResponse { sha: head_sha })
+    }
+
     async fn handle_get_branches(
         this: Entity<Self>,
         envelope: TypedEnvelope<proto::GitGetBranches>,
@@ -6052,13 +6068,21 @@ impl Repository {
     }
 
     pub fn head_sha(&mut self) -> oneshot::Receiver<Result<Option<String>>> {
+        let id = self.id;
         self.send_job(None, move |repo, _cx| async move {
             match repo {
                 RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
                     Ok(backend.head_sha().await)
                 }
-                RepositoryState::Remote(_) => {
-                    anyhow::bail!("head_sha is not supported for remote repositories")
+                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
+                    let response = client
+                        .request(proto::GitGetHeadSha {
+                            project_id: project_id.0,
+                            repository_id: id.to_proto(),
+                        })
+                        .await?;
+
+                    Ok(response.sha)
                 }
             }
         })

crates/proto/proto/git.proto 🔗

@@ -568,6 +568,15 @@ message GitGetWorktrees {
   uint64 repository_id = 2;
 }
 
+message GitGetHeadSha {
+  uint64 project_id = 1;
+  uint64 repository_id = 2;
+}
+
+message GitGetHeadShaResponse {
+  optional string sha = 1;
+}
+
 message GitWorktreesResponse {
   repeated Worktree worktrees = 1;
 }

crates/proto/proto/zed.proto 🔗

@@ -474,7 +474,9 @@ message Envelope {
     GitCompareCheckpoints git_compare_checkpoints = 436;
     GitCompareCheckpointsResponse git_compare_checkpoints_response = 437;
     GitDiffCheckpoints git_diff_checkpoints = 438;
-    GitDiffCheckpointsResponse git_diff_checkpoints_response = 439; // current max
+    GitDiffCheckpointsResponse git_diff_checkpoints_response = 439;
+    GitGetHeadSha git_get_head_sha = 440;
+    GitGetHeadShaResponse git_get_head_sha_response = 441; // current max
   }
 
   reserved 87 to 88;

crates/proto/src/proto.rs 🔗

@@ -351,6 +351,8 @@ messages!(
     (NewExternalAgentVersionAvailable, Background),
     (RemoteStarted, Background),
     (GitGetWorktrees, Background),
+    (GitGetHeadSha, Background),
+    (GitGetHeadShaResponse, Background),
     (GitWorktreesResponse, Background),
     (GitCreateWorktree, Background),
     (GitRemoveWorktree, Background),
@@ -558,6 +560,7 @@ request_messages!(
     (GetContextServerCommand, ContextServerCommand),
     (RemoteStarted, Ack),
     (GitGetWorktrees, GitWorktreesResponse),
+    (GitGetHeadSha, GitGetHeadShaResponse),
     (GitCreateWorktree, Ack),
     (GitRemoveWorktree, Ack),
     (GitRenameWorktree, Ack),
@@ -749,6 +752,7 @@ entity_messages!(
     ExternalAgentLoadingStatusUpdated,
     NewExternalAgentVersionAvailable,
     GitGetWorktrees,
+    GitGetHeadSha,
     GitCreateWorktree,
     GitRemoveWorktree,
     GitRenameWorktree,