Skip to content

Commit 5e30687

Browse files
committed
fix: Use revwalk to determine consecutive range
1 parent ae569cd commit 5e30687

File tree

3 files changed

+106
-26
lines changed

3 files changed

+106
-26
lines changed

asyncgit/src/sync/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ mod rebase;
2424
pub mod remotes;
2525
mod repository;
2626
mod reset;
27+
mod revwalk;
2728
mod reword;
2829
pub mod sign;
2930
mod staging;
@@ -65,6 +66,7 @@ pub use config::{
6566
};
6667
pub use diff::get_diff_commit;
6768
pub use git2::BranchType;
69+
pub use git2::Sort;
6870
pub use hooks::{
6971
hooks_commit_msg, hooks_post_commit, hooks_pre_commit,
7072
hooks_prepare_commit_msg, HookResult, PrepareCommitMsgSource,
@@ -87,6 +89,7 @@ pub use remotes::{
8789
pub(crate) use repository::repo;
8890
pub use repository::{RepoPath, RepoPathRef};
8991
pub use reset::{reset_repo, reset_stage, reset_workdir};
92+
pub use revwalk::revwalk;
9093
pub use reword::reword;
9194
pub use staging::{discard_lines, stage_lines};
9295
pub use stash::{

asyncgit/src/sync/revwalk.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use std::ops::Bound;
2+
3+
use crate::Result;
4+
use git2::{Commit, Oid};
5+
6+
use super::{repo, CommitId, RepoPath};
7+
8+
/// Performs a Git revision walk on `repo_path`, optionally bounded by `start` and `end` commits,
9+
/// sorted according to `sort`. The revwalk iterator bound by repository's lifetime is exposed through
10+
/// the `iter_fn`.
11+
///
12+
///
13+
pub fn revwalk<R>(
14+
repo_path: &RepoPath,
15+
start: Bound<&CommitId>,
16+
end: Bound<&CommitId>,
17+
sort: git2::Sort,
18+
iter_fn: impl FnOnce(
19+
&mut (dyn Iterator<Item = Result<Oid>>),
20+
) -> Result<R>,
21+
) -> Result<R> {
22+
let repo = repo(repo_path)?;
23+
let mut revwalk = repo.revwalk()?;
24+
revwalk.set_sorting(sort)?;
25+
let start = resolve(&repo, start)?;
26+
let end = resolve(&repo, end)?;
27+
28+
if let Some(s) = start {
29+
revwalk.hide(s.id())?;
30+
}
31+
if let Some(e) = end {
32+
revwalk.push(e.id())?;
33+
}
34+
let ret = iter_fn(&mut revwalk.map(|r| {
35+
r.map_err(|x| crate::Error::Generic(x.to_string()))
36+
}));
37+
ret
38+
}
39+
40+
fn resolve<'r>(
41+
repo: &'r git2::Repository,
42+
commit: Bound<&CommitId>,
43+
) -> Result<Option<Commit<'r>>> {
44+
match commit {
45+
Bound::Included(c) => {
46+
let commit = repo.find_commit(c.get_oid())?;
47+
Ok(Some(commit))
48+
}
49+
Bound::Excluded(s) => {
50+
let commit = repo.find_commit(s.get_oid())?;
51+
let res = (commit.parent_count() == 1)
52+
.then(|| commit.parent(0))
53+
.transpose()?;
54+
Ok(res)
55+
}
56+
Bound::Unbounded => Ok(None),
57+
}
58+
}

src/components/commitlist.rs

Lines changed: 45 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ use crate::{
1414
};
1515
use anyhow::Result;
1616
use asyncgit::sync::{
17-
self, checkout_commit, BranchDetails, BranchInfo, CommitId,
18-
RepoPathRef, Tags,
17+
self, checkout_commit, revwalk, BranchDetails, BranchInfo,
18+
CommitId, RepoPathRef, Sort, Tags,
1919
};
2020
use chrono::{DateTime, Local};
2121
use crossterm::event::Event;
@@ -29,8 +29,8 @@ use ratatui::{
2929
Frame,
3030
};
3131
use std::{
32-
borrow::Cow, cell::Cell, cmp, collections::BTreeMap, rc::Rc,
33-
time::Instant,
32+
borrow::Cow, cell::Cell, cmp, collections::BTreeMap, ops::Bound,
33+
rc::Rc, time::Instant,
3434
};
3535

3636
const ELEMENTS_PER_LINE: usize = 9;
@@ -137,37 +137,52 @@ impl CommitList {
137137
}
138138

139139
/// Build string of marked or selected (if none are marked) commit ids
140-
fn concat_selected_commit_ids(&self) -> Option<String> {
140+
fn concat_selected_commit_ids(&self) -> Result<Option<String>> {
141141
match self.marked.as_slice() {
142-
[] => self
142+
[] => Ok(self
143143
.items
144144
.iter()
145145
.nth(
146146
self.selection
147147
.saturating_sub(self.items.index_offset()),
148148
)
149-
.map(|e| e.id.to_string()),
150-
[latest, .., earliest]
151-
if self
152-
.marked()
153-
.windows(2)
154-
.all(|w| w[0].0 + 1 == w[1].0) =>
155-
{
156-
Some(format!("{}^..{}", earliest.1, latest.1))
149+
.map(|e| e.id.to_string())),
150+
[(_idx, commit)] => Ok(Some(commit.to_string())),
151+
[latest, .., earliest] => {
152+
let marked_rev = self.marked.iter().rev();
153+
let marked_topo_consecutive = revwalk(
154+
&self.repo.borrow(),
155+
Bound::Excluded(&earliest.1),
156+
Bound::Included(&latest.1),
157+
Sort::TOPOLOGICAL | Sort::REVERSE,
158+
|revwalk| {
159+
revwalk.zip(marked_rev).try_fold(
160+
true,
161+
|acc, (r, m)| {
162+
let revwalked = CommitId::new(r?);
163+
let marked = m.1;
164+
Ok(acc && (revwalked == marked))
165+
},
166+
)
167+
},
168+
)?;
169+
let yank = if marked_topo_consecutive {
170+
format!("{}^..{}", earliest.1, latest.1)
171+
} else {
172+
self.marked
173+
.iter()
174+
.map(|(_idx, commit)| commit.to_string())
175+
.join(" ")
176+
};
177+
Ok(Some(yank))
157178
}
158-
marked => Some(
159-
marked
160-
.iter()
161-
.map(|(_idx, commit)| commit.to_string())
162-
.join(" "),
163-
),
164179
}
165180
}
166181

167182
/// Copy currently marked or selected (if none are marked) commit ids
168183
/// to clipboard
169184
pub fn copy_commit_hash(&self) -> Result<()> {
170-
if let Some(yank) = self.concat_selected_commit_ids() {
185+
if let Some(yank) = self.concat_selected_commit_ids()? {
171186
crate::clipboard::copy_string(&yank)?;
172187
self.queue.push(InternalEvent::ShowInfoMsg(
173188
strings::copy_success(&yank),
@@ -1014,7 +1029,9 @@ mod tests {
10141029
#[test]
10151030
fn test_copy_commit_list_empty() {
10161031
assert_eq!(
1017-
CommitList::default().concat_selected_commit_ids(),
1032+
CommitList::default()
1033+
.concat_selected_commit_ids()
1034+
.unwrap(),
10181035
None
10191036
);
10201037
}
@@ -1029,7 +1046,7 @@ mod tests {
10291046
// offset by two, so we expect commit id 2 for
10301047
// selection = 4
10311048
assert_eq!(
1032-
cl.concat_selected_commit_ids(),
1049+
cl.concat_selected_commit_ids().unwrap(),
10331050
Some(String::from(
10341051
"0000000000000000000000000000000000000002"
10351052
))
@@ -1044,35 +1061,37 @@ mod tests {
10441061
..cl
10451062
};
10461063
assert_eq!(
1047-
cl.concat_selected_commit_ids(),
1064+
cl.concat_selected_commit_ids().unwrap(),
10481065
Some(String::from(
10491066
"0000000000000000000000000000000000000001",
10501067
))
10511068
);
10521069
}
10531070

10541071
#[test]
1072+
#[ignore = "needs real repository to run test. Will be moved to revwalk module."]
10551073
fn test_copy_commit_range_marked() {
10561074
let cl = build_commit_list_with_some_commits();
10571075
let cl = CommitList {
10581076
marked: build_marked_from_indices(&cl, &[4, 5, 6, 7]),
10591077
..cl
10601078
};
10611079
assert_eq!(
1062-
cl.concat_selected_commit_ids(),
1080+
cl.concat_selected_commit_ids().unwrap(),
10631081
Some(String::from("0000000000000000000000000000000000000005^..0000000000000000000000000000000000000002"))
10641082
);
10651083
}
10661084

10671085
#[test]
1086+
#[ignore = "needs real repository to run test. Will be moved to revwalk module."]
10681087
fn test_copy_commit_random_marked() {
10691088
let cl = build_commit_list_with_some_commits();
10701089
let cl = CommitList {
10711090
marked: build_marked_from_indices(&cl, &[4, 7]),
10721091
..cl
10731092
};
10741093
assert_eq!(
1075-
cl.concat_selected_commit_ids(),
1094+
cl.concat_selected_commit_ids().unwrap(),
10761095
Some(String::from(concat!(
10771096
"0000000000000000000000000000000000000002 ",
10781097
"0000000000000000000000000000000000000005"

0 commit comments

Comments
 (0)