Git activity indicator (#28204)

Closes #26182

Release Notes:

- Added an activity indicator for long-running git commands.

---------

Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
This commit is contained in:
Julia Ryan 2025-04-07 11:10:01 -07:00 committed by GitHub
parent 4f9f443452
commit e3830d2ef5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 625 additions and 487 deletions

View file

@ -88,7 +88,7 @@ impl BranchList {
) -> Self {
let all_branches_request = repository
.clone()
.map(|repository| repository.read(cx).branches());
.map(|repository| repository.update(cx, |repository, _| repository.branches()));
cx.spawn_in(window, async move |this, cx| {
let mut all_branches = all_branches_request
@ -202,10 +202,15 @@ impl BranchListDelegate {
return;
};
cx.spawn(async move |_, cx| {
cx.update(|cx| repo.read(cx).create_branch(new_branch_name.to_string()))?
.await??;
cx.update(|cx| repo.read(cx).change_branch(new_branch_name.to_string()))?
.await??;
repo.update(cx, |repo, _| {
repo.create_branch(new_branch_name.to_string())
})?
.await??;
repo.update(cx, |repo, _| {
repo.change_branch(new_branch_name.to_string())
})?
.await??;
Ok(())
})
.detach_and_prompt_err("Failed to create branch", window, cx, |e, _, _| {
@ -359,11 +364,13 @@ impl PickerDelegate for BranchListDelegate {
.ok_or_else(|| anyhow!("No active repository"))?
.clone();
let cx = cx.to_async();
let mut cx = cx.to_async();
anyhow::Ok(async move {
cx.update(|cx| repo.read(cx).change_branch(branch.name.to_string()))?
.await?
repo.update(&mut cx, |repo, _| {
repo.change_branch(branch.name.to_string())
})?
.await?
})
})??;

View file

@ -53,10 +53,8 @@ use project::{
};
use serde::{Deserialize, Serialize};
use settings::{Settings as _, SettingsStore};
use std::cell::RefCell;
use std::future::Future;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::{collections::HashSet, sync::Arc, time::Duration, usize};
use strum::{IntoEnumIterator, VariantNames};
use time::OffsetDateTime;
@ -64,7 +62,7 @@ use ui::{
Checkbox, ContextMenu, ElevationIndex, PopoverMenu, Scrollbar, ScrollbarState, Tooltip,
prelude::*,
};
use util::{ResultExt, TryFutureExt, maybe, post_inc};
use util::{ResultExt, TryFutureExt, maybe};
use workspace::AppState;
use notifications::status_toast::{StatusToast, ToastIcon};
@ -232,8 +230,6 @@ struct PendingOperation {
op_id: usize,
}
type RemoteOperations = Rc<RefCell<HashSet<u32>>>;
// computed state related to how to render scrollbars
// one per axis
// on render we just read this off the panel
@ -290,8 +286,6 @@ impl ScrollbarProperties {
}
pub struct GitPanel {
remote_operation_id: u32,
pending_remote_operations: RemoteOperations,
pub(crate) active_repository: Option<Entity<Repository>>,
pub(crate) commit_editor: Entity<Editor>,
conflicted_count: usize,
@ -327,17 +321,6 @@ pub struct GitPanel {
_settings_subscription: Subscription,
}
struct RemoteOperationGuard {
id: u32,
pending_remote_operations: RemoteOperations,
}
impl Drop for RemoteOperationGuard {
fn drop(&mut self) {
self.pending_remote_operations.borrow_mut().remove(&self.id);
}
}
const MAX_PANEL_EDITOR_LINES: usize = 6;
pub(crate) fn commit_message_editor(
@ -416,7 +399,7 @@ impl GitPanel {
) => {
this.schedule_update(*full_scan, window, cx);
}
GitStoreEvent::RepositoryUpdated(_, _, _) => {}
GitStoreEvent::RepositoryAdded(_) | GitStoreEvent::RepositoryRemoved(_) => {
this.schedule_update(false, window, cx);
}
@ -427,6 +410,8 @@ impl GitPanel {
})
.ok();
}
GitStoreEvent::RepositoryUpdated(_, _, _) => {}
GitStoreEvent::JobsUpdated => {}
},
)
.detach();
@ -458,8 +443,6 @@ impl GitPanel {
});
let mut git_panel = Self {
pending_remote_operations: Default::default(),
remote_operation_id: 0,
active_repository,
commit_editor,
conflicted_count: 0,
@ -671,16 +654,6 @@ impl GitPanel {
cx.notify();
}
fn start_remote_operation(&mut self) -> RemoteOperationGuard {
let id = post_inc(&mut self.remote_operation_id);
self.pending_remote_operations.borrow_mut().insert(id);
RemoteOperationGuard {
id,
pending_remote_operations: self.pending_remote_operations.clone(),
}
}
fn serialize(&mut self, cx: &mut Context<Self>) {
let width = self.width;
self.pending_serialization = cx.background_spawn(
@ -1743,7 +1716,6 @@ impl GitPanel {
return;
};
telemetry::event!("Git Fetched");
let guard = self.start_remote_operation();
let askpass = self.askpass_delegate("git fetch", window, cx);
let this = cx.weak_entity();
window
@ -1751,7 +1723,6 @@ impl GitPanel {
let fetch = repo.update(cx, |repo, cx| repo.fetch(askpass, cx))?;
let remote_message = fetch.await?;
drop(guard);
this.update(cx, |this, cx| {
let action = RemoteAction::Fetch;
match remote_message {
@ -1883,16 +1854,11 @@ impl GitPanel {
this.askpass_delegate(format!("git pull {}", remote.name), window, cx)
})?;
let guard = this
.update(cx, |this, _| this.start_remote_operation())
.ok();
let pull = repo.update(cx, |repo, cx| {
repo.pull(branch.name.clone(), remote.name.clone(), askpass, cx)
})?;
let remote_message = pull.await?;
drop(guard);
let action = RemoteAction::Pull(remote);
this.update(cx, |this, cx| match remote_message {
@ -1954,10 +1920,6 @@ impl GitPanel {
this.askpass_delegate(format!("git push {}", remote.name), window, cx)
})?;
let guard = this
.update(cx, |this, _| this.start_remote_operation())
.ok();
let push = repo.update(cx, |repo, cx| {
repo.push(
branch.name.clone(),
@ -1969,7 +1931,6 @@ impl GitPanel {
})?;
let remote_output = push.await?;
drop(guard);
let action = RemoteAction::Push(branch.name, remote);
this.update(cx, |this, cx| match remote_output {
@ -2590,20 +2551,6 @@ impl GitPanel {
workspace.add_item_to_center(Box::new(editor), window, cx);
}
pub fn render_spinner(&self) -> Option<impl IntoElement> {
(!self.pending_remote_operations.borrow().is_empty()).then(|| {
Icon::new(IconName::ArrowCircle)
.size(IconSize::XSmall)
.color(Color::Info)
.with_animation(
"arrow-circle",
Animation::new(Duration::from_secs(2)).repeat(),
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
)
.into_any_element()
})
}
pub fn can_commit(&self) -> bool {
(self.has_staged_changes() || self.has_tracked_changes()) && !self.has_unstaged_conflicts()
}
@ -2832,12 +2779,10 @@ impl GitPanel {
if !self.can_push_and_pull(cx) {
return None;
}
let spinner = self.render_spinner();
Some(
h_flex()
.gap_1()
.flex_shrink_0()
.children(spinner)
.when_some(branch, |this, branch| {
let focus_handle = Some(self.focus_handle(cx));
@ -4129,17 +4074,17 @@ impl RenderOnce for PanelRepoFooter {
.truncate(true)
.tooltip(Tooltip::for_action_title(
"Switch Branch",
&zed_actions::git::Branch,
&zed_actions::git::Switch,
))
.on_click(|_, window, cx| {
window.dispatch_action(zed_actions::git::Branch.boxed_clone(), cx);
window.dispatch_action(zed_actions::git::Switch.boxed_clone(), cx);
});
let branch_selector = PopoverMenu::new("popover-button")
.menu(move |window, cx| Some(branch_picker::popover(repo.clone(), window, cx)))
.trigger_with_tooltip(
branch_selector_button,
Tooltip::for_action_title("Switch Branch", &zed_actions::git::Branch),
Tooltip::for_action_title("Switch Branch", &zed_actions::git::Switch),
)
.anchor(Corner::BottomLeft)
.offset(gpui::Point {