@@ -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 ;
@@ -131,37 +131,52 @@ impl CommitList {
131131 }
132132
133133 /// Build string of marked or selected (if none are marked) commit ids
134- fn concat_selected_commit_ids ( & self ) -> Option < String > {
134+ fn concat_selected_commit_ids ( & self ) -> Result < Option < String > > {
135135 match self . marked . as_slice ( ) {
136- [ ] => self
136+ [ ] => Ok ( self
137137 . items
138138 . iter ( )
139139 . nth (
140140 self . selection
141141 . saturating_sub ( self . items . index_offset ( ) ) ,
142142 )
143- . map ( |e| e. id . to_string ( ) ) ,
144- [ latest, .., earliest]
145- if self
146- . marked ( )
147- . windows ( 2 )
148- . all ( |w| w[ 0 ] . 0 + 1 == w[ 1 ] . 0 ) =>
149- {
150- Some ( format ! ( "{}^..{}" , earliest. 1 , latest. 1 ) )
143+ . map ( |e| e. id . to_string ( ) ) ) ,
144+ [ ( _idx, commit) ] => Ok ( Some ( commit. to_string ( ) ) ) ,
145+ [ latest, .., earliest] => {
146+ let marked_rev = self . marked . iter ( ) . rev ( ) ;
147+ let marked_topo_consecutive = revwalk (
148+ & self . repo . borrow ( ) ,
149+ Bound :: Excluded ( & earliest. 1 ) ,
150+ Bound :: Included ( & latest. 1 ) ,
151+ Sort :: TOPOLOGICAL | Sort :: REVERSE ,
152+ |revwalk| {
153+ revwalk. zip ( marked_rev) . try_fold (
154+ true ,
155+ |acc, ( r, m) | {
156+ let revwalked = CommitId :: new ( r?) ;
157+ let marked = m. 1 ;
158+ Ok ( acc && ( revwalked == marked) )
159+ } ,
160+ )
161+ } ,
162+ ) ?;
163+ let yank = if marked_topo_consecutive {
164+ format ! ( "{}^..{}" , earliest. 1 , latest. 1 )
165+ } else {
166+ self . marked
167+ . iter ( )
168+ . map ( |( _idx, commit) | commit. to_string ( ) )
169+ . join ( " " )
170+ } ;
171+ Ok ( Some ( yank) )
151172 }
152- marked => Some (
153- marked
154- . iter ( )
155- . map ( |( _idx, commit) | commit. to_string ( ) )
156- . join ( " " ) ,
157- ) ,
158173 }
159174 }
160175
161176 /// Copy currently marked or selected (if none are marked) commit ids
162177 /// to clipboard
163178 pub fn copy_commit_hash ( & self ) -> Result < ( ) > {
164- if let Some ( yank) = self . concat_selected_commit_ids ( ) {
179+ if let Some ( yank) = self . concat_selected_commit_ids ( ) ? {
165180 crate :: clipboard:: copy_string ( & yank) ?;
166181 self . queue . push ( InternalEvent :: ShowInfoMsg (
167182 strings:: copy_success ( & yank) ,
@@ -1008,7 +1023,9 @@ mod tests {
10081023 #[ test]
10091024 fn test_copy_commit_list_empty ( ) {
10101025 assert_eq ! (
1011- CommitList :: default ( ) . concat_selected_commit_ids( ) ,
1026+ CommitList :: default ( )
1027+ . concat_selected_commit_ids( )
1028+ . unwrap( ) ,
10121029 None
10131030 ) ;
10141031 }
@@ -1023,7 +1040,7 @@ mod tests {
10231040 // offset by two, so we expect commit id 2 for
10241041 // selection = 4
10251042 assert_eq ! (
1026- cl. concat_selected_commit_ids( ) ,
1043+ cl. concat_selected_commit_ids( ) . unwrap ( ) ,
10271044 Some ( String :: from(
10281045 "0000000000000000000000000000000000000002"
10291046 ) )
@@ -1038,35 +1055,37 @@ mod tests {
10381055 ..cl
10391056 } ;
10401057 assert_eq ! (
1041- cl. concat_selected_commit_ids( ) ,
1058+ cl. concat_selected_commit_ids( ) . unwrap ( ) ,
10421059 Some ( String :: from(
10431060 "0000000000000000000000000000000000000001" ,
10441061 ) )
10451062 ) ;
10461063 }
10471064
10481065 #[ test]
1066+ #[ ignore = "needs real repository to run test. Will be moved to revwalk module." ]
10491067 fn test_copy_commit_range_marked ( ) {
10501068 let cl = build_commit_list_with_some_commits ( ) ;
10511069 let cl = CommitList {
10521070 marked : build_marked_from_indices ( & cl, & [ 4 , 5 , 6 , 7 ] ) ,
10531071 ..cl
10541072 } ;
10551073 assert_eq ! (
1056- cl. concat_selected_commit_ids( ) ,
1074+ cl. concat_selected_commit_ids( ) . unwrap ( ) ,
10571075 Some ( String :: from( "0000000000000000000000000000000000000005^..0000000000000000000000000000000000000002" ) )
10581076 ) ;
10591077 }
10601078
10611079 #[ test]
1080+ #[ ignore = "needs real repository to run test. Will be moved to revwalk module." ]
10621081 fn test_copy_commit_random_marked ( ) {
10631082 let cl = build_commit_list_with_some_commits ( ) ;
10641083 let cl = CommitList {
10651084 marked : build_marked_from_indices ( & cl, & [ 4 , 7 ] ) ,
10661085 ..cl
10671086 } ;
10681087 assert_eq ! (
1069- cl. concat_selected_commit_ids( ) ,
1088+ cl. concat_selected_commit_ids( ) . unwrap ( ) ,
10701089 Some ( String :: from( concat!(
10711090 "0000000000000000000000000000000000000002 " ,
10721091 "0000000000000000000000000000000000000005"
0 commit comments