git: Improvements to fetch/push/pull (#26041)

Cole Miller created

- Add global handlers so these actions can be invoked from the command
palette, etc.
- Tweak spinner to not show itself until a remote has been selected

Release Notes:

- N/A

Change summary

crates/git_ui/src/git_panel.rs | 19 +++++++++++--------
crates/git_ui/src/git_ui.rs    | 29 +++++++++++++++++++++++++++++
2 files changed, 40 insertions(+), 8 deletions(-)

Detailed changes

crates/git_ui/src/git_panel.rs 🔗

@@ -1364,7 +1364,7 @@ impl GitPanel {
         cx.notify();
     }
 
-    fn fetch(&mut self, _: &git::Fetch, _window: &mut Window, cx: &mut Context<Self>) {
+    pub(crate) fn fetch(&mut self, _: &git::Fetch, _window: &mut Window, cx: &mut Context<Self>) {
         let Some(repo) = self.active_repository.clone() else {
             return;
         };
@@ -1391,7 +1391,7 @@ impl GitPanel {
         .detach_and_log_err(cx);
     }
 
-    fn pull(&mut self, _: &git::Pull, window: &mut Window, cx: &mut Context<Self>) {
+    pub(crate) fn pull(&mut self, _: &git::Pull, window: &mut Window, cx: &mut Context<Self>) {
         let Some(repo) = self.active_repository.clone() else {
             return;
         };
@@ -1399,7 +1399,6 @@ impl GitPanel {
             return;
         };
         let branch = branch.clone();
-        let guard = self.start_remote_operation();
         let remote = self.get_current_remote(window, cx);
         cx.spawn(move |this, mut cx| async move {
             let remote = match remote.await {
@@ -1415,6 +1414,10 @@ impl GitPanel {
                 }
             };
 
+            let guard = this
+                .update(&mut cx, |this, _| this.start_remote_operation())
+                .ok();
+
             let pull = repo.update(&mut cx, |repo, _cx| {
                 repo.pull(branch.name.clone(), remote.name.clone())
             })?;
@@ -1435,7 +1438,7 @@ impl GitPanel {
         .detach_and_log_err(cx);
     }
 
-    fn push(&mut self, action: &git::Push, window: &mut Window, cx: &mut Context<Self>) {
+    pub(crate) fn push(&mut self, action: &git::Push, window: &mut Window, cx: &mut Context<Self>) {
         let Some(repo) = self.active_repository.clone() else {
             return;
         };
@@ -1443,7 +1446,6 @@ impl GitPanel {
             return;
         };
         let branch = branch.clone();
-        let guard = self.start_remote_operation();
         let options = action.options;
         let remote = self.get_current_remote(window, cx);
 
@@ -1461,6 +1463,10 @@ impl GitPanel {
                 }
             };
 
+            let guard = this
+                .update(&mut cx, |this, _| this.start_remote_operation())
+                .ok();
+
             let push = repo.update(&mut cx, |repo, _cx| {
                 repo.push(branch.name.clone(), remote.name.clone(), options)
             })?;
@@ -2716,9 +2722,6 @@ impl Render for GitPanel {
             .on_action(cx.listener(Self::unstage_all))
             .on_action(cx.listener(Self::restore_tracked_files))
             .on_action(cx.listener(Self::clean_all))
-            .on_action(cx.listener(Self::fetch))
-            .on_action(cx.listener(Self::pull))
-            .on_action(cx.listener(Self::push))
             .when(has_write_access && has_co_authors, |git_panel| {
                 git_panel.on_action(cx.listener(Self::toggle_fill_co_authors))
             })

crates/git_ui/src/git_ui.rs 🔗

@@ -4,6 +4,7 @@ use git_panel_settings::GitPanelSettings;
 use gpui::App;
 use project_diff::ProjectDiff;
 use ui::{ActiveTheme, Color, Icon, IconName, IntoElement};
+use workspace::Workspace;
 
 pub mod branch_picker;
 mod commit_modal;
@@ -19,6 +20,34 @@ pub fn init(cx: &mut App) {
     branch_picker::init(cx);
     cx.observe_new(ProjectDiff::register).detach();
     commit_modal::init(cx);
+
+    cx.observe_new(|workspace: &mut Workspace, _, _| {
+        workspace.register_action(|workspace, fetch: &git::Fetch, window, cx| {
+            let Some(panel) = workspace.panel::<git_panel::GitPanel>(cx) else {
+                return;
+            };
+            panel.update(cx, |panel, cx| {
+                panel.fetch(fetch, window, cx);
+            });
+        });
+        workspace.register_action(|workspace, push: &git::Push, window, cx| {
+            let Some(panel) = workspace.panel::<git_panel::GitPanel>(cx) else {
+                return;
+            };
+            panel.update(cx, |panel, cx| {
+                panel.push(push, window, cx);
+            });
+        });
+        workspace.register_action(|workspace, pull: &git::Pull, window, cx| {
+            let Some(panel) = workspace.panel::<git_panel::GitPanel>(cx) else {
+                return;
+            };
+            panel.update(cx, |panel, cx| {
+                panel.pull(pull, window, cx);
+            });
+        });
+    })
+    .detach();
 }
 
 // TODO: Add updated status colors to theme