From 4575d3429a29ad54110f5c667f1971dfe6e08c68 Mon Sep 17 00:00:00 2001 From: Esgariot Date: Tue, 3 Dec 2024 01:10:41 +0100 Subject: [PATCH 1/4] fix: Reverse commit range before yanking Produce `^..` when yanking consecutive range. Now, given consecutive marked selection, gitui's selection matches `git log`'s output in commit range. --- src/components/commitlist.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/commitlist.rs b/src/components/commitlist.rs index 52e4e7be9d..30875660db 100644 --- a/src/components/commitlist.rs +++ b/src/components/commitlist.rs @@ -134,7 +134,9 @@ impl CommitList { /// pub fn copy_commit_hash(&self) -> Result<()> { - let marked = self.marked.as_slice(); + let marked = self.marked.iter().rev().cloned().collect_vec(); + let marked = marked.as_slice(); + let yank: Option = match marked { [] => self .items @@ -147,7 +149,7 @@ impl CommitList { [(_idx, commit)] => Some(commit.to_string()), [first, .., last] => { let marked_consecutive = - marked.windows(2).all(|w| w[0].0 + 1 == w[1].0); + marked.windows(2).all(|w| w[0].0 - 1 == w[1].0); let yank = if marked_consecutive { format!("{}^..{}", first.1, last.1) From 8de1bf9955f4210554caebd7c8435aae899d79eb Mon Sep 17 00:00:00 2001 From: Esgariot Date: Tue, 3 Dec 2024 01:40:21 +0100 Subject: [PATCH 2/4] misc: Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 012b60e5ff..395d9f2f6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Breaking Changes +* reverse commit range before yanking marked commits, producing `^..` for consecutive commit ranges. [[@Esgariot](https://github.com/Esgariot)] ([#2441](https://github.com/extrawurst/gitui/pull/2441)) + ### Added * support loading custom syntax highlighting themes from a file [[@acuteenvy](https://github.com/acuteenvy)] ([#2565](https://github.com/gitui-org/gitui/pull/2565)) * Select syntax highlighting theme out of the defaults from syntect [[@vasilismanol](https://github.com/vasilismanol)] ([#1931](https://github.com/extrawurst/gitui/issues/1931)) From fb4750f9dfa331cc539b59db54b2c452469cb1e8 Mon Sep 17 00:00:00 2001 From: Esgariot Date: Sat, 5 Apr 2025 18:12:49 +0200 Subject: [PATCH 3/4] fix: Use revwalk to determine consecutive range --- asyncgit/src/sync/mod.rs | 3 ++ asyncgit/src/sync/revwalk.rs | 58 ++++++++++++++++++++++++++++++++++++ src/components/commitlist.rs | 39 ++++++++++++++++-------- 3 files changed, 87 insertions(+), 13 deletions(-) create mode 100644 asyncgit/src/sync/revwalk.rs diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs index c52a556aad..5f1733cba3 100644 --- a/asyncgit/src/sync/mod.rs +++ b/asyncgit/src/sync/mod.rs @@ -24,6 +24,7 @@ mod rebase; pub mod remotes; mod repository; mod reset; +mod revwalk; mod reword; pub mod sign; mod staging; @@ -65,6 +66,7 @@ pub use config::{ }; pub use diff::get_diff_commit; pub use git2::BranchType; +pub use git2::Sort; pub use hooks::{ hooks_commit_msg, hooks_post_commit, hooks_pre_commit, hooks_prepare_commit_msg, HookResult, PrepareCommitMsgSource, @@ -87,6 +89,7 @@ pub use remotes::{ pub(crate) use repository::repo; pub use repository::{RepoPath, RepoPathRef}; pub use reset::{reset_repo, reset_stage, reset_workdir}; +pub use revwalk::revwalk; pub use reword::reword; pub use staging::{discard_lines, stage_lines}; pub use stash::{ diff --git a/asyncgit/src/sync/revwalk.rs b/asyncgit/src/sync/revwalk.rs new file mode 100644 index 0000000000..cfa0c4da68 --- /dev/null +++ b/asyncgit/src/sync/revwalk.rs @@ -0,0 +1,58 @@ +use std::ops::Bound; + +use crate::Result; +use git2::{Commit, Oid}; + +use super::{repo, CommitId, RepoPath}; + +/// Performs a Git revision walk on `repo_path`, optionally bounded by `start` and `end` commits, +/// sorted according to `sort`. The revwalk iterator bound by repository's lifetime is exposed through +/// the `iter_fn`. +/// +/// +pub fn revwalk( + repo_path: &RepoPath, + start: Bound<&CommitId>, + end: Bound<&CommitId>, + sort: git2::Sort, + iter_fn: impl FnOnce( + &mut (dyn Iterator>), + ) -> Result, +) -> Result { + let repo = repo(repo_path)?; + let mut revwalk = repo.revwalk()?; + revwalk.set_sorting(sort)?; + let start = resolve(&repo, start)?; + let end = resolve(&repo, end)?; + + if let Some(s) = start { + revwalk.hide(s.id())?; + } + if let Some(e) = end { + revwalk.push(e.id())?; + } + let ret = iter_fn(&mut revwalk.map(|r| { + r.map_err(|x| crate::Error::Generic(x.to_string())) + })); + ret +} + +fn resolve<'r>( + repo: &'r git2::Repository, + commit: Bound<&CommitId>, +) -> Result>> { + match commit { + Bound::Included(c) => { + let commit = repo.find_commit(c.get_oid())?; + Ok(Some(commit)) + } + Bound::Excluded(s) => { + let commit = repo.find_commit(s.get_oid())?; + let res = (commit.parent_count() == 1) + .then(|| commit.parent(0)) + .transpose()?; + Ok(res) + } + Bound::Unbounded => Ok(None), + } +} diff --git a/src/components/commitlist.rs b/src/components/commitlist.rs index 30875660db..2e23e92de7 100644 --- a/src/components/commitlist.rs +++ b/src/components/commitlist.rs @@ -14,8 +14,8 @@ use crate::{ }; use anyhow::Result; use asyncgit::sync::{ - self, checkout_commit, BranchDetails, BranchInfo, CommitId, - RepoPathRef, Tags, + self, checkout_commit, revwalk, BranchDetails, BranchInfo, + CommitId, RepoPathRef, Sort, Tags, }; use chrono::{DateTime, Local}; use crossterm::event::Event; @@ -29,8 +29,8 @@ use ratatui::{ Frame, }; use std::{ - borrow::Cow, cell::Cell, cmp, collections::BTreeMap, rc::Rc, - time::Instant, + borrow::Cow, cell::Cell, cmp, collections::BTreeMap, ops::Bound, + rc::Rc, time::Instant, }; const ELEMENTS_PER_LINE: usize = 9; @@ -134,9 +134,7 @@ impl CommitList { /// pub fn copy_commit_hash(&self) -> Result<()> { - let marked = self.marked.iter().rev().cloned().collect_vec(); - let marked = marked.as_slice(); - + let marked = self.marked.as_slice(); let yank: Option = match marked { [] => self .items @@ -147,12 +145,27 @@ impl CommitList { ) .map(|e| e.id.to_string()), [(_idx, commit)] => Some(commit.to_string()), - [first, .., last] => { - let marked_consecutive = - marked.windows(2).all(|w| w[0].0 - 1 == w[1].0); - - let yank = if marked_consecutive { - format!("{}^..{}", first.1, last.1) + [latest, .., earliest] => { + let marked_rev = marked.iter().rev(); + let marked_topo_consecutive = revwalk( + &self.repo.borrow(), + Bound::Excluded(&earliest.1), + Bound::Included(&latest.1), + Sort::TOPOLOGICAL | Sort::REVERSE, + |revwalk| { + revwalk.zip(marked_rev).try_fold( + true, + |acc, (r, m)| { + let revwalked = CommitId::new(r?); + let marked = m.1; + log::trace!("comparing revwalk with marked: {} <-> {}",revwalked,marked); + Ok(acc && (revwalked == marked)) + }, + ) + }, + )?; + let yank = if marked_topo_consecutive { + format!("{}^..{}", earliest.1, latest.1) } else { marked .iter() From ab80e879e69b36b798f41edd9fe8ec7281ee3a5c Mon Sep 17 00:00:00 2001 From: Esgariot Date: Sat, 5 Apr 2025 18:13:00 +0200 Subject: [PATCH 4/4] misc: Formatting --- asyncgit/src/sync/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs index 5f1733cba3..e914e170f9 100644 --- a/asyncgit/src/sync/mod.rs +++ b/asyncgit/src/sync/mod.rs @@ -66,6 +66,7 @@ pub use config::{ }; pub use diff::get_diff_commit; pub use git2::BranchType; +pub use git2::ResetType; pub use git2::Sort; pub use hooks::{ hooks_commit_msg, hooks_post_commit, hooks_pre_commit, @@ -111,8 +112,6 @@ pub use utils::{ stage_add_all, stage_add_file, stage_addremoved, Head, }; -pub use git2::ResetType; - /// test utils #[cfg(test)] pub mod tests { @@ -126,6 +125,7 @@ pub mod tests { }; use crate::error::Result; use git2::Repository; + use std::{path::Path, process::Command}; use tempfile::TempDir;