@@ -14,8 +14,8 @@ use crate::{
1414} ;
1515use anyhow:: Result ;
1616use 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} ;
2020use chrono:: { DateTime , Local } ;
2121use crossterm:: event:: Event ;
@@ -29,8 +29,8 @@ use ratatui::{
2929 Frame ,
3030} ;
3131use 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
3636const 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