Show entries in remote git panels (#23773)

Kirill Bulatov created

Now both remote collab and ssh remote get entries shown and updated in
the git panel.
This seems to be quite a step towards remote git support, hence
submitting a PR.

Further steps: remove `get_local_repo` and allow getting the repo from
`Worktree`, not its local counterpart + have another, remote impl of the
`GitRepository` trait.

Release Notes:

- N/A

Change summary

crates/project/src/git.rs     | 41 ++++++++++++++++++++++--------------
crates/project/src/project.rs | 10 +++++++-
2 files changed, 33 insertions(+), 18 deletions(-)

Detailed changes

crates/project/src/git.rs 🔗

@@ -30,7 +30,7 @@ pub struct RepositoryHandle {
     git_state: WeakEntity<GitState>,
     worktree_id: WorktreeId,
     repository_entry: RepositoryEntry,
-    git_repo: Arc<dyn GitRepository>,
+    git_repo: Option<Arc<dyn GitRepository>>,
     commit_message: Entity<Buffer>,
     update_sender: mpsc::UnboundedSender<(Message, mpsc::Sender<anyhow::Error>)>,
 }
@@ -100,7 +100,7 @@ impl GitState {
 
         GitState {
             languages,
-            repositories: vec![],
+            repositories: Vec::new(),
             active_index: None,
             update_sender,
             _subscription,
@@ -128,13 +128,11 @@ impl GitState {
             for worktree in worktree_store.worktrees() {
                 worktree.update(cx, |worktree, cx| {
                     let snapshot = worktree.snapshot();
-                    let Some(local) = worktree.as_local() else {
-                        return;
-                    };
                     for repo in snapshot.repositories().iter() {
-                        let Some(local_repo) = local.get_local_repo(repo) else {
-                            continue;
-                        };
+                        let git_repo = worktree
+                            .as_local()
+                            .and_then(|local_worktree| local_worktree.get_local_repo(repo))
+                            .map(|local_repo| local_repo.repo().clone());
                         let existing = self
                             .repositories
                             .iter()
@@ -166,7 +164,7 @@ impl GitState {
                                 git_state: this.clone(),
                                 worktree_id: worktree.id(),
                                 repository_entry: repo.clone(),
-                                git_repo: local_repo.repo().clone(),
+                                git_repo,
                                 commit_message,
                                 update_sender: self.update_sender.clone(),
                             }
@@ -247,8 +245,11 @@ impl RepositoryHandle {
         if entries.is_empty() {
             return Ok(());
         }
+        let Some(git_repo) = self.git_repo.clone() else {
+            return Ok(());
+        };
         self.update_sender
-            .unbounded_send((Message::Stage(self.git_repo.clone(), entries), err_sender))
+            .unbounded_send((Message::Stage(git_repo, entries), err_sender))
             .map_err(|_| anyhow!("Failed to submit stage operation"))?;
         Ok(())
     }
@@ -261,8 +262,11 @@ impl RepositoryHandle {
         if entries.is_empty() {
             return Ok(());
         }
+        let Some(git_repo) = self.git_repo.clone() else {
+            return Ok(());
+        };
         self.update_sender
-            .unbounded_send((Message::Unstage(self.git_repo.clone(), entries), err_sender))
+            .unbounded_send((Message::Unstage(git_repo, entries), err_sender))
             .map_err(|_| anyhow!("Failed to submit unstage operation"))?;
         Ok(())
     }
@@ -314,11 +318,13 @@ impl RepositoryHandle {
     }
 
     pub fn commit(&self, mut err_sender: mpsc::Sender<anyhow::Error>, cx: &mut App) {
+        let Some(git_repo) = self.git_repo.clone() else {
+            return;
+        };
         let message = self.commit_message.read(cx).as_rope().clone();
-        let result = self.update_sender.unbounded_send((
-            Message::Commit(self.git_repo.clone(), message),
-            err_sender.clone(),
-        ));
+        let result = self
+            .update_sender
+            .unbounded_send((Message::Commit(git_repo, message), err_sender.clone()));
         if result.is_err() {
             cx.spawn(|_| async move {
                 err_sender
@@ -335,6 +341,9 @@ impl RepositoryHandle {
     }
 
     pub fn commit_all(&self, mut err_sender: mpsc::Sender<anyhow::Error>, cx: &mut App) {
+        let Some(git_repo) = self.git_repo.clone() else {
+            return;
+        };
         let to_stage = self
             .repository_entry
             .status()
@@ -343,7 +352,7 @@ impl RepositoryHandle {
             .collect::<Vec<_>>();
         let message = self.commit_message.read(cx).as_rope().clone();
         let result = self.update_sender.unbounded_send((
-            Message::StageAndCommit(self.git_repo.clone(), message, to_stage),
+            Message::StageAndCommit(git_repo, message, to_stage),
             err_sender.clone(),
         ));
         if result.is_err() {

crates/project/src/project.rs 🔗

@@ -814,6 +814,9 @@ impl Project {
             });
             cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach();
 
+            let git_state =
+                Some(cx.new(|cx| GitState::new(&worktree_store, languages.clone(), cx)));
+
             cx.subscribe(&ssh, Self::on_ssh_event).detach();
             cx.observe(&ssh, |_, _, cx| cx.notify()).detach();
 
@@ -826,7 +829,7 @@ impl Project {
                 lsp_store,
                 join_project_response_message_id: 0,
                 client_state: ProjectClientState::Local,
-                git_state: None,
+                git_state,
                 client_subscriptions: Vec::new(),
                 _subscriptions: vec![
                     cx.on_release(Self::release),
@@ -1009,6 +1012,9 @@ impl Project {
             SettingsObserver::new_remote(worktree_store.clone(), task_store.clone(), cx)
         })?;
 
+        let git_state =
+            Some(cx.new(|cx| GitState::new(&worktree_store, languages.clone(), cx))).transpose()?;
+
         let this = cx.new(|cx| {
             let replica_id = response.payload.replica_id as ReplicaId;
 
@@ -1059,7 +1065,7 @@ impl Project {
                     remote_id,
                     replica_id,
                 },
-                git_state: None,
+                git_state,
                 buffers_needing_diff: Default::default(),
                 git_diff_debouncer: DebouncedDelay::new(),
                 terminals: Terminals {