From a426515d2b36f2cd04c042c47db0861a03fd5f7e Mon Sep 17 00:00:00 2001 From: wuyangfan <1102042793@qq.com> Date: Sun, 17 May 2026 16:49:40 +0800 Subject: [PATCH] feat: toggle hiding merge commits in revlog Press Shift+M on the log tab to filter merge commits (like git log --no-merges). Rebuilds the async log walk with a parent-count filter. Fixes #2830 Co-authored-by: Cursor --- asyncgit/src/revlog.rs | 7 ++++ asyncgit/src/sync/commit_filter.rs | 12 +++++++ asyncgit/src/sync/mod.rs | 3 +- src/components/commitlist.rs | 4 +++ src/keys/key_list.rs | 2 ++ src/strings.rs | 26 ++++++++++++-- src/tabs/revlog.rs | 54 ++++++++++++++++++++++++++---- 7 files changed, 99 insertions(+), 9 deletions(-) diff --git a/asyncgit/src/revlog.rs b/asyncgit/src/revlog.rs index 774d5140ef..18d183a8c6 100644 --- a/asyncgit/src/revlog.rs +++ b/asyncgit/src/revlog.rs @@ -145,6 +145,13 @@ impl AsyncLog { Ok(false) } + /// + pub fn invalidate(&self) { + if let Ok(mut head) = self.current_head.lock() { + *head = None; + } + } + /// pub fn fetch(&self) -> Result { self.background.store(false, Ordering::Relaxed); diff --git a/asyncgit/src/sync/commit_filter.rs b/asyncgit/src/sync/commit_filter.rs index 229e41d01d..3615aa549a 100644 --- a/asyncgit/src/sync/commit_filter.rs +++ b/asyncgit/src/sync/commit_filter.rs @@ -222,3 +222,15 @@ pub fn filter_commit_by_search( }, )) } + +/// +pub fn filter_commits_exclude_merges() -> SharedCommitFilterFn { + Arc::new(Box::new( + move |repo: &Repository, + commit_id: &CommitId| + -> Result { + let commit = repo.find_commit((*commit_id).into())?; + Ok(commit.parent_count() <= 1) + }, + )) +} diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs index 2a5f413e8f..b3d0c93e43 100644 --- a/asyncgit/src/sync/mod.rs +++ b/asyncgit/src/sync/mod.rs @@ -51,7 +51,8 @@ pub use commit_details::{ }; pub use commit_files::get_commit_files; pub use commit_filter::{ - diff_contains_file, filter_commit_by_search, LogFilterSearch, + diff_contains_file, filter_commit_by_search, + filter_commits_exclude_merges, LogFilterSearch, LogFilterSearchOptions, SearchFields, SearchOptions, SharedCommitFilterFn, }; diff --git a/src/components/commitlist.rs b/src/components/commitlist.rs index a84060151d..a430ea1fa3 100644 --- a/src/components/commitlist.rs +++ b/src/components/commitlist.rs @@ -62,6 +62,10 @@ pub struct CommitList { impl CommitList { /// + pub fn set_title(&mut self, title: impl Into>) { + self.title = title.into(); + } + pub fn new(env: &Environment, title: &str) -> Self { Self { repo: env.repo.clone(), diff --git a/src/keys/key_list.rs b/src/keys/key_list.rs index 24a9507a49..1a5a8f1043 100644 --- a/src/keys/key_list.rs +++ b/src/keys/key_list.rs @@ -88,6 +88,7 @@ pub struct KeysList { pub log_reset_commit: GituiKeyEvent, pub log_reword_commit: GituiKeyEvent, pub log_find: GituiKeyEvent, + pub log_hide_merges: GituiKeyEvent, pub find_commit_sha: GituiKeyEvent, pub commit_amend: GituiKeyEvent, pub toggle_signoff: GituiKeyEvent, @@ -186,6 +187,7 @@ impl Default for KeysList { log_reset_commit: GituiKeyEvent { code: KeyCode::Char('R'), modifiers: KeyModifiers::SHIFT }, log_reword_commit: GituiKeyEvent { code: KeyCode::Char('r'), modifiers: KeyModifiers::empty() }, log_find: GituiKeyEvent { code: KeyCode::Char('f'), modifiers: KeyModifiers::empty() }, + log_hide_merges: GituiKeyEvent::new(KeyCode::Char('M'), KeyModifiers::SHIFT), find_commit_sha: GituiKeyEvent::new(KeyCode::Char('j'), KeyModifiers::CONTROL), commit_amend: GituiKeyEvent::new(KeyCode::Char('a'), KeyModifiers::CONTROL), toggle_signoff: GituiKeyEvent::new(KeyCode::Char('s'), KeyModifiers::CONTROL), diff --git a/src/strings.rs b/src/strings.rs index f66a9e93f5..fc4f61a9d4 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -300,8 +300,12 @@ pub fn confirm_msg_force_push( "Confirm force push to branch '{branch_ref}' ? This may rewrite history." ) } -pub fn log_title(_key_config: &SharedKeyConfig) -> String { - "Commit".to_string() +pub fn log_title(hide_merge_commits: bool) -> String { + if hide_merge_commits { + "Commit (no merges)".to_string() + } else { + "Commit".to_string() + } } pub fn file_log_title( file_path: &str, @@ -1576,6 +1580,24 @@ pub mod commands { CMD_GROUP_LOG, ) } + pub fn log_toggle_hide_merges( + key_config: &SharedKeyConfig, + hide_merge_commits: bool, + ) -> CommandText { + let action = if hide_merge_commits { + "show merge commits" + } else { + "hide merge commits" + }; + CommandText::new( + format!( + "No merges [{}]", + key_config.get_hint(key_config.keys.log_hide_merges), + ), + action, + CMD_GROUP_LOG, + ) + } pub fn log_find_commit( key_config: &SharedKeyConfig, ) -> CommandText { diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 9200c93f00..27a68f9540 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -16,8 +16,8 @@ use anyhow::Result; use asyncgit::{ asyncjob::AsyncSingleJob, sync::{ - self, filter_commit_by_search, CommitId, LogFilterSearch, - LogFilterSearchOptions, RepoPathRef, + self, filter_commit_by_search, filter_commits_exclude_merges, + CommitId, LogFilterSearch, LogFilterSearchOptions, RepoPathRef, }, AsyncBranchesJob, AsyncCommitFilterJob, AsyncGitNotification, AsyncLog, AsyncTags, CommitFilesParams, FetchStatus, @@ -66,6 +66,7 @@ pub struct Revlog { list: CommitList, git_log: AsyncLog, search: LogSearch, + hide_merge_commits: bool, git_tags: AsyncTags, git_local_branches: AsyncSingleJob, git_remote_branches: AsyncSingleJob, @@ -83,16 +84,14 @@ impl Revlog { repo: env.repo.clone(), queue: env.queue.clone(), commit_details: CommitDetailsComponent::new(env), - list: CommitList::new( - env, - &strings::log_title(&env.key_config), - ), + list: CommitList::new(env, &strings::log_title(false)), git_log: AsyncLog::new( env.repo.borrow().clone(), &env.sender_git, None, ), search: LogSearch::Off, + hide_merge_commits: false, git_tags: AsyncTags::new( env.repo.borrow().clone(), &env.sender_git, @@ -404,6 +403,34 @@ impl Revlog { fn can_start_search(&self) -> bool { !self.git_log.is_pending() && !self.is_search_pending() } + + fn can_toggle_hide_merges(&self) -> bool { + matches!(self.search, LogSearch::Off) && !self.git_log.is_pending() + } + + fn toggle_hide_merge_commits(&mut self) { + if !self.can_toggle_hide_merges() { + return; + } + + self.hide_merge_commits = !self.hide_merge_commits; + let filter = if self.hide_merge_commits { + Some(filter_commits_exclude_merges()) + } else { + None + }; + + self.git_log = AsyncLog::new( + self.repo.borrow().clone(), + &self.sender, + filter, + ); + self.git_log.invalidate(); + self.list.clear(); + self.list + .set_title(strings::log_title(self.hide_merge_commits)); + let _ = self.git_log.fetch(); + } } impl DrawableComponent for Revlog { @@ -575,6 +602,13 @@ impl Component for Revlog { self.queue .push(InternalEvent::OpenLogSearchPopup); return Ok(EventState::Consumed); + } else if key_match( + k, + self.key_config.keys.log_hide_merges, + ) && self.can_toggle_hide_merges() + { + self.toggle_hide_merge_commits(); + return Ok(EventState::Consumed); } else if key_match( k, self.key_config.keys.compare_commits, @@ -729,6 +763,14 @@ impl Component for Revlog { self.can_start_search(), self.visible || force_all, )); + out.push(CommandInfo::new( + strings::commands::log_toggle_hide_merges( + &self.key_config, + self.hide_merge_commits, + ), + self.can_toggle_hide_merges(), + self.visible || force_all, + )); visibility_blocking(self) }