diff --git a/crates/git_ui/src/git_graph.rs b/crates/git_ui/src/git_graph.rs index 90efa57d228609..bc7ea439a49454 100644 --- a/crates/git_ui/src/git_graph.rs +++ b/crates/git_ui/src/git_graph.rs @@ -1,6 +1,7 @@ use crate::{ commit_tooltip::{CommitAvatar, CommitDetails, CommitTooltip}, commit_view::CommitView, + git_panel_settings::GitPanelSettings, git_status_icon, }; use collections::{BTreeMap, HashMap, IndexSet}; @@ -39,6 +40,7 @@ use search::{ SearchOption, SearchOptions, SearchSource, SelectNextMatch, SelectPreviousMatch, ToggleCaseSensitive, buffer_search, }; +use settings::{Settings, update_settings_file}; use smallvec::{SmallVec, smallvec}; use std::{ cell::Cell, @@ -51,8 +53,8 @@ use task::{ResolvedTask, TaskContext, TaskVariables, VariableName}; use theme::AccentColors; use time::{OffsetDateTime, UtcOffset, format_description::BorrowedFormatItem}; use ui::{ - Chip, ColumnWidthConfig, CommonAnimationExt as _, ContextMenu, ContextMenuEntry, DiffStat, - Divider, HeaderResizeInfo, HighlightedLabel, IndentGuideColors, ListItem, ListItemSpacing, + Checkbox, Chip, ColumnWidthConfig, CommonAnimationExt as _, ContextMenu, ContextMenuEntry, + DiffStat, Divider, HeaderResizeInfo, HighlightedLabel, IndentGuideColors, ListItem, ListItemSpacing, RedistributableColumnsState, ScrollableHandle, Table, TableInteractionState, TableRenderContext, TableResizeBehavior, Tooltip, WithScrollbar, bind_redistributable_columns, prelude::*, redistribute_hidden_fractions, redistribute_hidden_widths, @@ -1338,9 +1340,52 @@ pub struct GitGraph { changed_files_view_mode: ChangedFilesViewMode, changed_files_expanded_dirs: HashMap, pending_select_sha: Option, + show_stashes_in_graph: bool, } impl GitGraph { + fn should_show_stashes(cx: &App) -> bool { + GitPanelSettings::get_global(cx).show_stashes_in_graph + } + + fn is_stash_commit(commit: &InitialGraphCommitData) -> bool { + commit + .ref_names + .iter() + .any(|ref_name| ref_name.as_ref() == "refs/stash") + } + + fn visible_graph_commits( + &self, + commits: &[Arc], + ) -> Vec> { + if self.show_stashes_in_graph { + commits.to_vec() + } else { + commits + .iter() + .filter(|commit| !Self::is_stash_commit(commit)) + .cloned() + .collect() + } + } + + fn toggle_show_stashes_in_graph(&mut self, cx: &mut Context) { + let new_value = !self.show_stashes_in_graph; + self.show_stashes_in_graph = new_value; + + if let Some(workspace) = self.workspace.upgrade() { + let fs = workspace.read(cx).app_state().fs.clone(); + update_settings_file(fs, cx, move |settings, _| { + settings.git_panel.get_or_insert_default().show_stashes_in_graph = Some(new_value); + }); + } + + self.pending_select_sha = None; + self.invalidate_state(cx); + self.fetch_initial_graph_data(cx); + } + fn invalidate_state(&mut self, cx: &mut Context) { self.graph_data.clear(); self.search_state.matches.clear(); @@ -1536,9 +1581,11 @@ impl GitGraph { }, ); let mut row_height = Self::row_height(window, cx); + let mut show_stashes_in_graph = Self::should_show_stashes(cx); cx.observe_global_in::(window, move |this, window, cx| { let new_row_height = Self::row_height(window, cx); + let new_show_stashes_in_graph = Self::should_show_stashes(cx); if new_row_height != row_height { // The `uniform_list` powering the table caches the item size // from its last layout; invalidate it so it re-measures with @@ -1549,6 +1596,13 @@ impl GitGraph { row_height = new_row_height; cx.notify(); } + if new_show_stashes_in_graph != show_stashes_in_graph { + show_stashes_in_graph = new_show_stashes_in_graph; + this.show_stashes_in_graph = new_show_stashes_in_graph; + this.pending_select_sha = None; + this.invalidate_state(cx); + this.fetch_initial_graph_data(cx); + } }) .detach(); @@ -1584,6 +1638,7 @@ impl GitGraph { changed_files_view_mode: ChangedFilesViewMode::default(), changed_files_expanded_dirs: HashMap::default(), pending_select_sha: None, + show_stashes_in_graph: Self::should_show_stashes(cx), }; this.fetch_initial_graph_data(cx); @@ -1639,7 +1694,8 @@ impl GitGraph { old_count..*commit_count, cx, ); - self.graph_data.add_commits(commits); + let commits = self.visible_graph_commits(commits); + self.graph_data.add_commits(&commits); let pending_sha_index = self.pending_select_sha.and_then(|oid| { repository.get_graph_data(source.clone(), *order).and_then( @@ -1671,7 +1727,9 @@ impl GitGraph { self.invalidate_state(cx); } } - RepositoryEvent::StashEntriesChanged if self.log_source == LogSource::All => { + RepositoryEvent::StashEntriesChanged + if self.log_source == LogSource::All && self.show_stashes_in_graph => + { // Stash entries initial's scan id is 2, so we don't want to invalidate the graph before that if repository.read(cx).scan_id > 2 { self.pending_select_sha = None; @@ -1689,7 +1747,8 @@ impl GitGraph { let commits = repository .graph_data(self.log_source.clone(), self.log_order, 0..usize::MAX, cx) .commits; - self.graph_data.add_commits(commits); + let commits = self.visible_graph_commits(commits); + self.graph_data.add_commits(&commits); }); } } @@ -2747,6 +2806,7 @@ impl GitGraph { let focus_handle = self.focus_handle.clone(); let git_graph = cx.entity(); + let show_stashes_in_graph = self.show_stashes_in_graph; let context_menu = ContextMenu::build(window, cx, |mut context_menu, _window, _cx| { context_menu = context_menu.context(focus_handle).header("Columns"); for (col_idx, label) in columns.iter().enumerate() { @@ -2768,6 +2828,20 @@ impl GitGraph { }, ); } + if !is_path_history { + let git_graph = git_graph.clone(); + context_menu = context_menu.separator().toggleable_entry( + "Show Stashes", + show_stashes_in_graph, + IconPosition::End, + None, + move |_window, cx| { + git_graph.update(cx, |this, cx| { + this.toggle_show_stashes_in_graph(cx); + }); + }, + ); + } context_menu }); @@ -2905,6 +2979,23 @@ impl GitGraph { ), ), ) + .when(matches!(self.log_source, LogSource::All), |this| { + this.child( + h_flex() + .gap_1() + .items_center() + .child( + Checkbox::new( + "git-graph-show-stashes", + self.show_stashes_in_graph.into(), + ) + .on_click(cx.listener(|this, _, _, cx| { + this.toggle_show_stashes_in_graph(cx); + })), + ) + .child(Label::new("Show stashes").size(LabelSize::Small)), + ) + }) } fn render_loading_spinner(&self, cx: &App) -> AnyElement { @@ -6587,6 +6678,85 @@ mod tests { assert_eq!(reloaded_shas, vec![updated_head, updated_stash]); } + #[gpui::test] + async fn test_git_graph_hides_stashes_when_setting_disabled(cx: &mut TestAppContext) { + init_test(cx); + + cx.update(|cx| { + cx.update_global::(|store, cx| { + store.update_user_settings(cx, |settings| { + settings.git_panel.get_or_insert_default().show_stashes_in_graph = Some(false); + }); + }); + }); + + let fs = FakeFs::new(cx.executor()); + fs.insert_tree( + Path::new("/project"), + json!({ + ".git": {}, + "file.txt": "content", + }), + ) + .await; + + let head = Oid::from_bytes(&[1; 20]).unwrap(); + let stash = Oid::from_bytes(&[2; 20]).unwrap(); + + fs.set_graph_commits( + Path::new("/project/.git"), + vec![ + Arc::new(InitialGraphCommitData { + sha: head, + parents: smallvec![stash], + ref_names: vec!["HEAD".into(), "refs/heads/main".into()], + }), + Arc::new(InitialGraphCommitData { + sha: stash, + parents: smallvec![], + ref_names: vec!["refs/stash".into()], + }), + ], + ); + + let project = Project::test(fs.clone(), [Path::new("/project")], cx).await; + cx.run_until_parked(); + + let repository = project.read_with(cx, |project, cx| { + project + .active_repository(cx) + .expect("should have a repository") + }); + + let (multi_workspace, cx) = cx.add_window_view(|window, cx| { + workspace::MultiWorkspace::test_new(project.clone(), window, cx) + }); + let workspace_weak = + multi_workspace.read_with(&*cx, |multi, _| multi.workspace().downgrade()); + let git_graph = cx.new_window_entity(|window, cx| { + GitGraph::new( + repository.read(cx).id, + project.read(cx).git_store().clone(), + workspace_weak, + None, + window, + cx, + ) + }); + cx.run_until_parked(); + + let shas = git_graph.read_with(&*cx, |graph, _| { + graph + .graph_data + .commits + .iter() + .map(|commit| commit.data.sha) + .collect::>() + }); + + assert_eq!(shas, vec![head]); + } + #[gpui::test] async fn test_git_graph_row_at_position_rounding(cx: &mut TestAppContext) { init_test(cx); diff --git a/crates/git_ui/src/git_panel_settings.rs b/crates/git_ui/src/git_panel_settings.rs index 0dbde43cfb5eb6..ff2768038ec3ad 100644 --- a/crates/git_ui/src/git_panel_settings.rs +++ b/crates/git_ui/src/git_panel_settings.rs @@ -29,6 +29,7 @@ pub struct GitPanelSettings { pub sort_by: GitPanelSortBy, pub group_by: GitPanelGroupBy, pub collapse_untracked_diff: bool, + pub show_stashes_in_graph: bool, pub tree_view: bool, pub diff_stats: bool, pub show_count_badge: bool, @@ -78,6 +79,7 @@ impl Settings for GitPanelSettings { sort_by: git_panel.sort_by.unwrap(), group_by: git_panel.group_by.unwrap(), collapse_untracked_diff: git_panel.collapse_untracked_diff.unwrap(), + show_stashes_in_graph: git_panel.show_stashes_in_graph.unwrap_or(true), tree_view: git_panel.tree_view.unwrap(), diff_stats: git_panel.diff_stats.unwrap(), show_count_badge: git_panel.show_count_badge.unwrap(), diff --git a/crates/settings_content/src/settings_content.rs b/crates/settings_content/src/settings_content.rs index f0f652d2baa4ad..23f07a470d8ec1 100644 --- a/crates/settings_content/src/settings_content.rs +++ b/crates/settings_content/src/settings_content.rs @@ -690,6 +690,11 @@ pub struct GitPanelSettingsContent { /// Default: false pub collapse_untracked_diff: Option, + /// Whether to show stash entries in the Git Graph. + /// + /// Default: true + pub show_stashes_in_graph: Option, + /// Whether to show entries with tree or flat view in the panel /// /// Default: false diff --git a/crates/settings_ui/src/page_data.rs b/crates/settings_ui/src/page_data.rs index 2f6ce0beec5b3d..b5d3f6c76262b4 100644 --- a/crates/settings_ui/src/page_data.rs +++ b/crates/settings_ui/src/page_data.rs @@ -5840,7 +5840,7 @@ fn panels_page() -> SettingsPage { ] } - fn git_panel_section() -> [SettingsPageItem; 17] { + fn git_panel_section() -> [SettingsPageItem; 18] { [ SettingsPageItem::SectionHeader("Git Panel"), SettingsPageItem::SettingItem(SettingItem { @@ -5983,6 +5983,29 @@ fn panels_page() -> SettingsPage { metadata: None, files: USER, }), + SettingsPageItem::SettingItem(SettingItem { + title: "Show Stashes In Git Graph", + description: "Whether to include stash entries when viewing the Git Graph.", + field: Box::new(SettingField { + organization_override: None, + json_path: Some("git_panel.show_stashes_in_graph"), + pick: |settings_content| { + settings_content + .git_panel + .as_ref()? + .show_stashes_in_graph + .as_ref() + }, + write: |settings_content, value, _| { + settings_content + .git_panel + .get_or_insert_default() + .show_stashes_in_graph = value; + }, + }), + metadata: None, + files: USER, + }), SettingsPageItem::SettingItem(SettingItem { title: "Tree View", description: "Enable to show entries in tree view list, disable to show in flat view list.",