From 24c432299c0e7de632f8e0f60d6a6844fae68701 Mon Sep 17 00:00:00 2001 From: Danilo Leal Date: Tue, 24 Feb 2026 18:45:44 -0300 Subject: [PATCH 1/9] Add new commit and graph icons --- assets/icons/git_commit.svg | 5 +++++ assets/icons/git_graph.svg | 7 +++++-- crates/icons/src/icons.rs | 1 + 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 assets/icons/git_commit.svg diff --git a/assets/icons/git_commit.svg b/assets/icons/git_commit.svg new file mode 100644 index 00000000000000..38b36ec7efb722 --- /dev/null +++ b/assets/icons/git_commit.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/git_graph.svg b/assets/icons/git_graph.svg index 8f372a305d3fdd..7ae33e365d40bf 100644 --- a/assets/icons/git_graph.svg +++ b/assets/icons/git_graph.svg @@ -1,4 +1,7 @@ - - + + + + + diff --git a/crates/icons/src/icons.rs b/crates/icons/src/icons.rs index 9ed9a8b658cc8b..d6356f831ea9bb 100644 --- a/crates/icons/src/icons.rs +++ b/crates/icons/src/icons.rs @@ -142,6 +142,7 @@ pub enum IconName { GitBranch, GitBranchAlt, GitBranchPlus, + GitCommit, GitGraph, Github, Hash, From ea1b190daf03b21b9dcb282ec297960120719bcb Mon Sep 17 00:00:00 2001 From: Danilo Leal Date: Tue, 24 Feb 2026 18:46:28 -0300 Subject: [PATCH 2/9] Allow opening the commit view through the Git Graph commit drawer --- crates/git_graph/src/git_graph.rs | 97 ++++++++++++++++++++++++------- crates/git_ui/src/git_panel.rs | 7 +++ 2 files changed, 82 insertions(+), 22 deletions(-) diff --git a/crates/git_graph/src/git_graph.rs b/crates/git_graph/src/git_graph.rs index 37f170ada5ecd2..727cf2f94a4d2c 100644 --- a/crates/git_graph/src/git_graph.rs +++ b/crates/git_graph/src/git_graph.rs @@ -39,7 +39,7 @@ use ui::{ }; use workspace::{ Workspace, - item::{Item, ItemEvent, SerializableItem}, + item::{Item, ItemEvent, SerializableItem, TabTooltipContent}, }; const COMMIT_CIRCLE_RADIUS: Pixels = px(3.5); @@ -710,29 +710,66 @@ pub fn init(cx: &mut App) { |div| { let workspace = workspace.weak_handle(); - div.on_action(move |_: &git_ui::git_panel::Open, window, cx| { - workspace - .update(cx, |workspace, cx| { - let existing = workspace.items_of_type::(cx).next(); - if let Some(existing) = existing { - workspace.activate_item(&existing, true, true, window, cx); - return; - } + div.on_action({ + let workspace = workspace.clone(); + move |_: &git_ui::git_panel::Open, window, cx| { + workspace + .update(cx, |workspace, cx| { + let existing = workspace.items_of_type::(cx).next(); + if let Some(existing) = existing { + workspace.activate_item(&existing, true, true, window, cx); + return; + } - let project = workspace.project().clone(); - let workspace_handle = workspace.weak_handle(); - let git_graph = cx - .new(|cx| GitGraph::new(project, workspace_handle, window, cx)); - workspace.add_item_to_active_pane( - Box::new(git_graph), - None, - true, - window, - cx, - ); - }) - .ok(); + let project = workspace.project().clone(); + let workspace_handle = workspace.weak_handle(); + let git_graph = cx.new(|cx| { + GitGraph::new(project, workspace_handle, window, cx) + }); + workspace.add_item_to_active_pane( + Box::new(git_graph), + None, + true, + window, + cx, + ); + }) + .ok(); + } }) + .on_action( + move |action: &git_ui::git_panel::OpenAtCommit, window, cx| { + let sha = action.sha.clone(); + workspace + .update(cx, |workspace, cx| { + let existing = workspace.items_of_type::(cx).next(); + if let Some(existing) = existing { + existing.update(cx, |graph, cx| { + graph.select_commit_by_sha(&sha, cx); + }); + workspace.activate_item(&existing, true, true, window, cx); + return; + } + + let project = workspace.project().clone(); + let workspace_handle = workspace.weak_handle(); + let git_graph = cx.new(|cx| { + let mut graph = + GitGraph::new(project, workspace_handle, window, cx); + graph.select_commit_by_sha(&sha, cx); + graph + }); + workspace.add_item_to_active_pane( + Box::new(git_graph), + None, + true, + window, + cx, + ); + }) + .ok(); + }, + ) }, ) }); @@ -821,6 +858,7 @@ pub struct GitGraph { commit_details_split_state: Entity, selected_repo_id: Option, changed_files_scroll_handle: UniformListScrollHandle, + pending_select_sha: Option, } impl GitGraph { @@ -918,6 +956,7 @@ impl GitGraph { commit_details_split_state: cx.new(|_cx| SplitState::new()), selected_repo_id: active_repository, changed_files_scroll_handle: UniformListScrollHandle::new(), + pending_select_sha: None, }; this.fetch_initial_graph_data(cx); @@ -944,6 +983,9 @@ impl GitGraph { self.graph_data.add_commits(commits); }); cx.notify(); + if let Some(sha) = self.pending_select_sha.take() { + self.select_commit_by_sha(&sha, cx); + } } RepositoryEvent::BranchChanged | RepositoryEvent::MergeHeadsChanged => { // Only invalidate if we scanned atleast once, @@ -1153,6 +1195,17 @@ impl GitGraph { cx.notify(); } + pub fn select_commit_by_sha(&mut self, sha: &str, cx: &mut Context) { + for (idx, commit) in self.graph_data.commits.iter().enumerate() { + if commit.data.sha.to_string() == sha { + self.pending_select_sha = None; + self.select_entry(idx, cx); + return; + } + } + self.pending_select_sha = Some(sha.to_string()); + } + fn open_selected_commit_view(&mut self, window: &mut Window, cx: &mut Context) { let Some(selected_entry_index) = self.selected_entry_idx else { return; diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index b8caf478305609..3f560641ce0281 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -123,6 +123,13 @@ actions!( ] ); +/// Opens the Git Graph Tab at a specific commit. +#[derive(Clone, PartialEq, serde::Deserialize, schemars::JsonSchema, gpui::Action)] +#[action(namespace = git_graph)] +pub struct OpenAtCommit { + pub sha: String, +} + fn prompt( msg: &str, detail: Option<&str>, From 97b24228a438b381499a9e24904c0806febe5833 Mon Sep 17 00:00:00 2001 From: Danilo Leal Date: Tue, 24 Feb 2026 18:46:41 -0300 Subject: [PATCH 3/9] Customize Git Graph tab icon and tooltip --- crates/git_graph/src/git_graph.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/crates/git_graph/src/git_graph.rs b/crates/git_graph/src/git_graph.rs index 727cf2f94a4d2c..f8a0099b3dceee 100644 --- a/crates/git_graph/src/git_graph.rs +++ b/crates/git_graph/src/git_graph.rs @@ -2232,6 +2232,30 @@ impl Focusable for GitGraph { impl Item for GitGraph { type Event = ItemEvent; + fn tab_icon(&self, _window: &Window, _cx: &App) -> Option { + Some(Icon::new(IconName::GitGraph)) + } + + fn tab_tooltip_content(&self, cx: &App) -> Option { + let repo_name = self.get_selected_repository(cx).and_then(|repo| { + repo.read(cx) + .work_directory_abs_path + .file_name() + .map(|name| name.to_string_lossy().to_string()) + }); + + Some(TabTooltipContent::Custom(Box::new(Tooltip::element({ + move |_, _| { + v_flex() + .child(Label::new("Git Graph")) + .when_some(repo_name.clone(), |this, name| { + this.child(Label::new(name).color(Color::Muted).size(LabelSize::Small)) + }) + .into_any_element() + } + })))) + } + fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { "Git Graph".into() } From a94a473b34b9937f6196ee448b0245774c2fdf19 Mon Sep 17 00:00:00 2001 From: Danilo Leal Date: Tue, 24 Feb 2026 18:46:57 -0300 Subject: [PATCH 4/9] Add button for Git Graph in commit view and tweak header --- Cargo.lock | 1 + crates/git_ui/Cargo.toml | 1 + crates/git_ui/src/commit_view.rs | 276 +++++++++++++++++-------------- 3 files changed, 150 insertions(+), 128 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 934e0d1a01482d..1e08f56edb8a1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7208,6 +7208,7 @@ dependencies = [ "ctor", "db", "editor", + "feature_flags", "futures 0.3.31", "fuzzy", "git", diff --git a/crates/git_ui/Cargo.toml b/crates/git_ui/Cargo.toml index f779570be471fd..28fac0f849a487 100644 --- a/crates/git_ui/Cargo.toml +++ b/crates/git_ui/Cargo.toml @@ -27,6 +27,7 @@ component.workspace = true db.workspace = true editor.workspace = true futures.workspace = true +feature_flags.workspace = true fuzzy.workspace = true git.workspace = true gpui.workspace = true diff --git a/crates/git_ui/src/commit_view.rs b/crates/git_ui/src/commit_view.rs index f5ed23a6a84e76..a850e6a827ffee 100644 --- a/crates/git_ui/src/commit_view.rs +++ b/crates/git_ui/src/commit_view.rs @@ -3,6 +3,7 @@ use buffer_diff::BufferDiff; use collections::HashMap; use editor::display_map::{BlockPlacement, BlockProperties, BlockStyle}; use editor::{Addon, Editor, EditorEvent, ExcerptRange, MultiBuffer, multibuffer_context_lines}; +use feature_flags::{FeatureFlag, FeatureFlagAppExt as _}; use git::repository::{CommitDetails, CommitDiff, RepoPath, is_binary_content}; use git::status::{FileStatus, StatusCode, TrackedStatus}; use git::{ @@ -27,7 +28,7 @@ use std::{ sync::Arc, }; use theme::ActiveTheme; -use ui::{ButtonLike, DiffStat, Tooltip, prelude::*}; +use ui::{ButtonLike, DiffStat, Divider, Tooltip, prelude::*}; use util::{ResultExt, paths::PathStyle, rel_path::RelPath, truncate_and_trailoff}; use workspace::item::TabTooltipContent; use workspace::{ @@ -42,6 +43,12 @@ use workspace::{ use crate::commit_tooltip::CommitAvatar; use crate::git_panel::GitPanel; +struct GitGraphFeatureFlag; + +impl FeatureFlag for GitGraphFeatureFlag { + const NAME: &'static str = "git-graph"; +} + actions!(git, [ApplyCurrentStash, PopCurrentStash, DropCurrentStash,]); pub fn init(cx: &mut App) { @@ -450,6 +457,7 @@ impl CommitView { fn render_header(&self, window: &mut Window, cx: &mut Context) -> impl IntoElement { let commit = &self.commit; let author_name = commit.author_name.clone(); + let author_email = commit.author_email.clone(); let commit_sha = commit.sha.clone(); let commit_date = time::OffsetDateTime::from_unix_timestamp(commit.commit_timestamp) .unwrap_or_else(|_| time::OffsetDateTime::now_utc()); @@ -461,36 +469,6 @@ impl CommitView { time_format::TimestampFormat::MediumAbsolute, ); - let remote_info = self - .remote - .as_ref() - .filter(|_| self.stash.is_none()) - .map(|remote| { - let provider = remote.host.name(); - let parsed_remote = ParsedGitRemote { - owner: remote.owner.as_ref().into(), - repo: remote.repo.as_ref().into(), - }; - let params = BuildCommitPermalinkParams { sha: &commit.sha }; - let url = remote - .host - .build_commit_permalink(&parsed_remote, params) - .to_string(); - (provider, url) - }); - - let (additions, deletions) = self.calculate_changed_lines(cx); - - let commit_diff_stat = if additions > 0 || deletions > 0 { - Some(DiffStat::new( - "commit-diff-stat", - additions as usize, - deletions as usize, - )) - } else { - None - }; - let gutter_width = self.editor.update(cx, |editor, cx| { let snapshot = editor.snapshot(window, cx); let style = editor.style(cx); @@ -501,115 +479,70 @@ impl CommitView { .full_width() }); - let clipboard_has_link = cx + let clipboard_has_sha = cx .read_from_clipboard() .and_then(|entry| entry.text()) .map_or(false, |clipboard_text| { clipboard_text.trim() == commit_sha.as_ref() }); - let (copy_icon, copy_icon_color) = if clipboard_has_link { + let (copy_icon, copy_icon_color) = if clipboard_has_sha { (IconName::Check, Color::Success) } else { (IconName::Copy, Color::Muted) }; h_flex() + .py_2() + .pr_2p5() + .w_full() + .justify_between() .border_b_1() .border_color(cx.theme().colors().border_variant) - .w_full() .child( h_flex() - .w(gutter_width) - .justify_center() - .child(self.render_commit_avatar(&commit.sha, rems_from_px(48.), window, cx)), + .child(h_flex().w(gutter_width).justify_center().child( + self.render_commit_avatar(&commit.sha, rems_from_px(40.), window, cx), + )) + .child( + v_flex().child(Label::new(author_name)).child( + h_flex() + .gap_1p5() + .child( + Label::new(date_string) + .color(Color::Muted) + .size(LabelSize::Small), + ) + .child( + Label::new("•") + .size(LabelSize::Small) + .color(Color::Muted) + .alpha(0.5), + ) + .child( + Label::new(author_email) + .color(Color::Muted) + .size(LabelSize::Small), + ), + ), + ), ) .child( - h_flex() - .py_4() - .pl_1() - .pr_4() - .w_full() - .items_start() - .justify_between() - .flex_wrap() - .child( - v_flex() - .child( - h_flex() - .gap_1() - .child(Label::new(author_name).color(Color::Default)) - .child({ - ButtonLike::new("sha") - .child( - h_flex() - .group("sha_btn") - .size_full() - .max_w_32() - .gap_0p5() - .child( - Label::new(commit_sha.clone()) - .color(Color::Muted) - .size(LabelSize::Small) - .truncate() - .buffer_font(cx), - ) - .child( - div().visible_on_hover("sha_btn").child( - Icon::new(copy_icon) - .color(copy_icon_color) - .size(IconSize::Small), - ), - ), - ) - .tooltip({ - let commit_sha = commit_sha.clone(); - move |_, cx| { - Tooltip::with_meta( - "Copy Commit SHA", - None, - commit_sha.clone(), - cx, - ) - } - }) - .on_click(move |_, _, cx| { - cx.stop_propagation(); - cx.write_to_clipboard(ClipboardItem::new_string( - commit_sha.to_string(), - )); - }) - }), - ) - .child( - h_flex() - .gap_1p5() - .child( - Label::new(date_string) - .color(Color::Muted) - .size(LabelSize::Small), - ) - .child( - Label::new("•") - .color(Color::Ignored) - .size(LabelSize::Small), - ) - .children(commit_diff_stat), - ), - ) - .children(remote_info.map(|(provider_name, url)| { - let icon = match provider_name.as_str() { - "GitHub" => IconName::Github, - _ => IconName::Link, - }; - - Button::new("view_on_provider", format!("View on {}", provider_name)) - .icon(icon) - .icon_color(Color::Muted) - .icon_size(IconSize::Small) - .icon_position(IconPosition::Start) - .on_click(move |_, _, cx| cx.open_url(&url)) - })), + Button::new("sha", "Commit SHA") + .icon(copy_icon) + .icon_color(copy_icon_color) + .icon_position(IconPosition::Start) + .icon_size(IconSize::Small) + .tooltip({ + let commit_sha = commit_sha.clone(); + move |_, cx| { + Tooltip::with_meta("Copy Commit SHA", None, commit_sha.clone(), cx) + } + }) + .on_click(move |_, _, cx| { + cx.stop_propagation(); + cx.write_to_clipboard(ClipboardItem::new_string(commit_sha.to_string())); + }), ) } @@ -898,7 +831,7 @@ impl Item for CommitView { type Event = EditorEvent; fn tab_icon(&self, _window: &Window, _cx: &App) -> Option { - Some(Icon::new(IconName::GitBranch).color(Color::Muted)) + Some(Icon::new(IconName::GitCommit).color(Color::Muted)) } fn tab_content(&self, params: TabContentParams, _window: &Window, cx: &App) -> AnyElement { @@ -1081,8 +1014,96 @@ impl CommitViewToolbar { impl EventEmitter for CommitViewToolbar {} impl Render for CommitViewToolbar { - fn render(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { - div().hidden() + fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + let Some(commit_view) = self.commit_view.as_ref().and_then(|w| w.upgrade()) else { + return div(); + }; + + let is_stash = commit_view.read(cx).stash.is_some(); + + if is_stash { + return div(); + } + + let commit_view_ref = commit_view.read(cx); + + let (additions, deletions) = commit_view_ref.calculate_changed_lines(cx); + + let commit_sha = commit_view_ref.commit.sha.clone(); + + let remote_info = commit_view_ref.remote.as_ref().map(|remote| { + let provider = remote.host.name(); + let parsed_remote = ParsedGitRemote { + owner: remote.owner.as_ref().into(), + repo: remote.repo.as_ref().into(), + }; + let params = BuildCommitPermalinkParams { sha: &commit_sha }; + let url = remote + .host + .build_commit_permalink(&parsed_remote, params) + .to_string(); + (provider, url) + }); + + let sha_for_graph = commit_sha.to_string(); + + h_flex() + .gap_1() + .child( + h_flex() + .gap_2() + .when(additions > 0 || deletions > 0, |this| { + this.child(DiffStat::new( + "toolbar-diff-stat", + additions as usize, + deletions as usize, + )) + }) + .child(Divider::vertical()), + ) + .child( + IconButton::new("buffer-search", IconName::MagnifyingGlass) + .icon_size(IconSize::Small) + .tooltip(move |_, cx| { + Tooltip::for_action( + "Buffer Search", + &zed_actions::buffer_search::Deploy::find(), + cx, + ) + }) + .on_click(|_, window, cx| { + window.dispatch_action( + Box::new(zed_actions::buffer_search::Deploy::find()), + cx, + ); + }), + ) + .when(cx.has_flag::(), |el| { + el.child( + IconButton::new("show-in-git-graph", IconName::GitGraph) + .icon_size(IconSize::Small) + .tooltip(Tooltip::text("Show in Git Graph")) + .on_click(move |_, window, cx| { + window.dispatch_action( + Box::new(crate::git_panel::OpenAtCommit { + sha: sha_for_graph.clone(), + }), + cx, + ); + }), + ) + }) + .children(remote_info.map(|(provider_name, url)| { + let icon = match provider_name.as_str() { + "GitHub" => IconName::Github, + _ => IconName::Link, + }; + + IconButton::new("view_on_provider", icon) + .icon_size(IconSize::Small) + .tooltip(Tooltip::text(format!("View on {}", provider_name))) + .on_click(move |_, _, cx| cx.open_url(&url)) + })) } } @@ -1093,12 +1114,11 @@ impl ToolbarItemView for CommitViewToolbar { _: &mut Window, cx: &mut Context, ) -> ToolbarItemLocation { - if let Some(entity) = active_pane_item.and_then(|i| i.act_as::(cx)) - && entity.read(cx).stash.is_some() - { + if let Some(entity) = active_pane_item.and_then(|i| i.act_as::(cx)) { self.commit_view = Some(entity.downgrade()); return ToolbarItemLocation::PrimaryRight; } + self.commit_view = None; ToolbarItemLocation::Hidden } From 4e4f17f880fd19c4cfe25ff82a8ed7b2965e2000 Mon Sep 17 00:00:00 2001 From: Danilo Leal Date: Tue, 24 Feb 2026 18:57:02 -0300 Subject: [PATCH 5/9] Add Graph feature flag to shared `flags.rs` --- crates/feature_flags/src/flags.rs | 6 ++++++ crates/git_graph/src/git_graph.rs | 8 +------- crates/git_ui/src/commit_view.rs | 30 ++++++++++-------------------- 3 files changed, 17 insertions(+), 27 deletions(-) diff --git a/crates/feature_flags/src/flags.rs b/crates/feature_flags/src/flags.rs index 8f96de0e7b6d9b..087e76c4129254 100644 --- a/crates/feature_flags/src/flags.rs +++ b/crates/feature_flags/src/flags.rs @@ -57,6 +57,12 @@ impl FeatureFlag for DiffReviewFeatureFlag { } } +pub struct GitGraphFeatureFlag; + +impl FeatureFlag for GitGraphFeatureFlag { + const NAME: &'static str = "git-graph"; +} + pub struct StreamingEditFileToolFeatureFlag; impl FeatureFlag for StreamingEditFileToolFeatureFlag { diff --git a/crates/git_graph/src/git_graph.rs b/crates/git_graph/src/git_graph.rs index f8a0099b3dceee..ec3d32e1a66a2d 100644 --- a/crates/git_graph/src/git_graph.rs +++ b/crates/git_graph/src/git_graph.rs @@ -1,5 +1,5 @@ use collections::{BTreeMap, HashMap}; -use feature_flags::{FeatureFlag, FeatureFlagAppExt as _}; +use feature_flags::{FeatureFlagAppExt as _, GitGraphFeatureFlag}; use git::{ BuildCommitPermalinkParams, GitHostingProviderRegistry, GitRemote, Oid, ParsedGitRemote, parse_git_remote_url, @@ -246,12 +246,6 @@ actions!( ] ); -pub struct GitGraphFeatureFlag; - -impl FeatureFlag for GitGraphFeatureFlag { - const NAME: &'static str = "git-graph"; -} - fn timestamp_format() -> &'static [BorrowedFormatItem<'static>] { static FORMAT: OnceLock>> = OnceLock::new(); FORMAT.get_or_init(|| { diff --git a/crates/git_ui/src/commit_view.rs b/crates/git_ui/src/commit_view.rs index a850e6a827ffee..58d34f7f05dbf4 100644 --- a/crates/git_ui/src/commit_view.rs +++ b/crates/git_ui/src/commit_view.rs @@ -3,7 +3,7 @@ use buffer_diff::BufferDiff; use collections::HashMap; use editor::display_map::{BlockPlacement, BlockProperties, BlockStyle}; use editor::{Addon, Editor, EditorEvent, ExcerptRange, MultiBuffer, multibuffer_context_lines}; -use feature_flags::{FeatureFlag, FeatureFlagAppExt as _}; +use feature_flags::{FeatureFlagAppExt as _, GitGraphFeatureFlag}; use git::repository::{CommitDetails, CommitDiff, RepoPath, is_binary_content}; use git::status::{FileStatus, StatusCode, TrackedStatus}; use git::{ @@ -28,7 +28,7 @@ use std::{ sync::Arc, }; use theme::ActiveTheme; -use ui::{ButtonLike, DiffStat, Divider, Tooltip, prelude::*}; +use ui::{DiffStat, Divider, Tooltip, prelude::*}; use util::{ResultExt, paths::PathStyle, rel_path::RelPath, truncate_and_trailoff}; use workspace::item::TabTooltipContent; use workspace::{ @@ -43,12 +43,6 @@ use workspace::{ use crate::commit_tooltip::CommitAvatar; use crate::git_panel::GitPanel; -struct GitGraphFeatureFlag; - -impl FeatureFlag for GitGraphFeatureFlag { - const NAME: &'static str = "git-graph"; -} - actions!(git, [ApplyCurrentStash, PopCurrentStash, DropCurrentStash,]); pub fn init(cx: &mut App) { @@ -1049,18 +1043,14 @@ impl Render for CommitViewToolbar { h_flex() .gap_1() - .child( - h_flex() - .gap_2() - .when(additions > 0 || deletions > 0, |this| { - this.child(DiffStat::new( - "toolbar-diff-stat", - additions as usize, - deletions as usize, - )) - }) - .child(Divider::vertical()), - ) + .when(additions > 0 || deletions > 0, |this| { + this.child(h_flex().gap_2().child(DiffStat::new( + "toolbar-diff-stat", + additions as usize, + deletions as usize, + ))) + .child(Divider::vertical()) + }) .child( IconButton::new("buffer-search", IconName::MagnifyingGlass) .icon_size(IconSize::Small) From f35dd511da0df0695bad7842554abe1d70e9958f Mon Sep 17 00:00:00 2001 From: Danilo Leal Date: Tue, 24 Feb 2026 19:04:33 -0300 Subject: [PATCH 6/9] Show diff stat and search button in stash view, too --- crates/git_ui/src/commit_view.rs | 57 +++++++++++++++----------------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/crates/git_ui/src/commit_view.rs b/crates/git_ui/src/commit_view.rs index 58d34f7f05dbf4..c61a9cba1f4701 100644 --- a/crates/git_ui/src/commit_view.rs +++ b/crates/git_ui/src/commit_view.rs @@ -1013,13 +1013,8 @@ impl Render for CommitViewToolbar { return div(); }; - let is_stash = commit_view.read(cx).stash.is_some(); - - if is_stash { - return div(); - } - let commit_view_ref = commit_view.read(cx); + let is_stash = commit_view_ref.stash.is_some(); let (additions, deletions) = commit_view_ref.calculate_changed_lines(cx); @@ -1068,32 +1063,34 @@ impl Render for CommitViewToolbar { ); }), ) - .when(cx.has_flag::(), |el| { - el.child( - IconButton::new("show-in-git-graph", IconName::GitGraph) + .when(!is_stash, |this| { + this.when(cx.has_flag::(), |this| { + this.child( + IconButton::new("show-in-git-graph", IconName::GitGraph) + .icon_size(IconSize::Small) + .tooltip(Tooltip::text("Show in Git Graph")) + .on_click(move |_, window, cx| { + window.dispatch_action( + Box::new(crate::git_panel::OpenAtCommit { + sha: sha_for_graph.clone(), + }), + cx, + ); + }), + ) + }) + .children(remote_info.map(|(provider_name, url)| { + let icon = match provider_name.as_str() { + "GitHub" => IconName::Github, + _ => IconName::Link, + }; + + IconButton::new("view_on_provider", icon) .icon_size(IconSize::Small) - .tooltip(Tooltip::text("Show in Git Graph")) - .on_click(move |_, window, cx| { - window.dispatch_action( - Box::new(crate::git_panel::OpenAtCommit { - sha: sha_for_graph.clone(), - }), - cx, - ); - }), - ) + .tooltip(Tooltip::text(format!("View on {}", provider_name))) + .on_click(move |_, _, cx| cx.open_url(&url)) + })) }) - .children(remote_info.map(|(provider_name, url)| { - let icon = match provider_name.as_str() { - "GitHub" => IconName::Github, - _ => IconName::Link, - }; - - IconButton::new("view_on_provider", icon) - .icon_size(IconSize::Small) - .tooltip(Tooltip::text(format!("View on {}", provider_name))) - .on_click(move |_, _, cx| cx.open_url(&url)) - })) } } From 14928c7c0600bf038358605d3f203e1b05ec9534 Mon Sep 17 00:00:00 2001 From: Danilo Leal Date: Tue, 24 Feb 2026 19:09:17 -0300 Subject: [PATCH 7/9] Hide the commit SHA button in the stash view --- crates/git_ui/src/commit_view.rs | 54 ++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/crates/git_ui/src/commit_view.rs b/crates/git_ui/src/commit_view.rs index c61a9cba1f4701..8f2a019fddf051 100644 --- a/crates/git_ui/src/commit_view.rs +++ b/crates/git_ui/src/commit_view.rs @@ -521,23 +521,27 @@ impl CommitView { ), ), ) - .child( - Button::new("sha", "Commit SHA") - .icon(copy_icon) - .icon_color(copy_icon_color) - .icon_position(IconPosition::Start) - .icon_size(IconSize::Small) - .tooltip({ - let commit_sha = commit_sha.clone(); - move |_, cx| { - Tooltip::with_meta("Copy Commit SHA", None, commit_sha.clone(), cx) - } - }) - .on_click(move |_, _, cx| { - cx.stop_propagation(); - cx.write_to_clipboard(ClipboardItem::new_string(commit_sha.to_string())); - }), - ) + .when(self.stash.is_none(), |this| { + this.child( + Button::new("sha", "Commit SHA") + .icon(copy_icon) + .icon_color(copy_icon_color) + .icon_position(IconPosition::Start) + .icon_size(IconSize::Small) + .tooltip({ + let commit_sha = commit_sha.clone(); + move |_, cx| { + Tooltip::with_meta("Copy Commit SHA", None, commit_sha.clone(), cx) + } + }) + .on_click(move |_, _, cx| { + cx.stop_propagation(); + cx.write_to_clipboard(ClipboardItem::new_string( + commit_sha.to_string(), + )); + }), + ) + }) } fn apply_stash(workspace: &mut Workspace, window: &mut Window, cx: &mut App) { @@ -1039,12 +1043,16 @@ impl Render for CommitViewToolbar { h_flex() .gap_1() .when(additions > 0 || deletions > 0, |this| { - this.child(h_flex().gap_2().child(DiffStat::new( - "toolbar-diff-stat", - additions as usize, - deletions as usize, - ))) - .child(Divider::vertical()) + this.child( + h_flex() + .gap_2() + .child(DiffStat::new( + "toolbar-diff-stat", + additions as usize, + deletions as usize, + )) + .child(Divider::vertical()), + ) }) .child( IconButton::new("buffer-search", IconName::MagnifyingGlass) From 452ac1885a7160af3614fccf3d221c1e2a1c210c Mon Sep 17 00:00:00 2001 From: Danilo Leal Date: Tue, 24 Feb 2026 19:23:50 -0300 Subject: [PATCH 8/9] Optimize `select_commit_by_sha` function --- crates/git_graph/src/git_graph.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/git_graph/src/git_graph.rs b/crates/git_graph/src/git_graph.rs index ec3d32e1a66a2d..9cd740e4cc515f 100644 --- a/crates/git_graph/src/git_graph.rs +++ b/crates/git_graph/src/git_graph.rs @@ -1190,8 +1190,11 @@ impl GitGraph { } pub fn select_commit_by_sha(&mut self, sha: &str, cx: &mut Context) { + let Ok(oid) = sha.parse::() else { + return; + }; for (idx, commit) in self.graph_data.commits.iter().enumerate() { - if commit.data.sha.to_string() == sha { + if commit.data.sha == oid { self.pending_select_sha = None; self.select_entry(idx, cx); return; From e510eaaff589fd153cff62548dcc1d138161b8d7 Mon Sep 17 00:00:00 2001 From: Danilo Leal Date: Tue, 24 Feb 2026 19:42:12 -0300 Subject: [PATCH 9/9] Fix issues with `pending_select_sha ` Clear `pending_select_sha` on branch changes, and limit retries so it expires if the target SHA is never found in the graph. --- crates/git_graph/src/git_graph.rs | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/crates/git_graph/src/git_graph.rs b/crates/git_graph/src/git_graph.rs index 9cd740e4cc515f..3bdb2b0d717ca4 100644 --- a/crates/git_graph/src/git_graph.rs +++ b/crates/git_graph/src/git_graph.rs @@ -48,6 +48,7 @@ const LANE_WIDTH: Pixels = px(16.0); const LEFT_PADDING: Pixels = px(12.0); const LINE_WIDTH: Pixels = px(1.5); const RESIZE_HANDLE_WIDTH: f32 = 8.0; +const PENDING_SELECT_MAX_RETRIES: usize = 5; const COPIED_STATE_DURATION: Duration = Duration::from_secs(2); struct CopiedState { @@ -852,7 +853,7 @@ pub struct GitGraph { commit_details_split_state: Entity, selected_repo_id: Option, changed_files_scroll_handle: UniformListScrollHandle, - pending_select_sha: Option, + pending_select_sha: Option<(String, usize)>, } impl GitGraph { @@ -977,11 +978,10 @@ impl GitGraph { self.graph_data.add_commits(commits); }); cx.notify(); - if let Some(sha) = self.pending_select_sha.take() { - self.select_commit_by_sha(&sha, cx); - } + self.retry_pending_select(cx); } RepositoryEvent::BranchChanged | RepositoryEvent::MergeHeadsChanged => { + self.pending_select_sha = None; // Only invalidate if we scanned atleast once, // meaning we are not inside the initial repo loading state // NOTE: this fixes an loading performance regression @@ -1200,7 +1200,24 @@ impl GitGraph { return; } } - self.pending_select_sha = Some(sha.to_string()); + self.pending_select_sha = Some((sha.to_string(), PENDING_SELECT_MAX_RETRIES)); + } + + fn retry_pending_select(&mut self, cx: &mut Context) { + let Some((sha, retries_remaining)) = self.pending_select_sha.take() else { + return; + }; + if let Ok(oid) = sha.parse::() { + for (idx, commit) in self.graph_data.commits.iter().enumerate() { + if commit.data.sha == oid { + self.select_entry(idx, cx); + return; + } + } + } + if retries_remaining > 0 { + self.pending_select_sha = Some((sha, retries_remaining - 1)); + } } fn open_selected_commit_view(&mut self, window: &mut Window, cx: &mut Context) {