@@ -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) ,
@@ -1007,7 +1022,9 @@ mod tests {
10071022 #[ test]
10081023 fn test_copy_commit_list_empty ( ) {
10091024 assert_eq ! (
1010- CommitList :: default ( ) . concat_selected_commit_ids( ) ,
1025+ CommitList :: default ( )
1026+ . concat_selected_commit_ids( )
1027+ . unwrap( ) ,
10111028 None
10121029 ) ;
10131030 }
@@ -1022,7 +1039,7 @@ mod tests {
10221039 // offset by two, so we expect commit id 2 for
10231040 // selection = 4
10241041 assert_eq ! (
1025- cl. concat_selected_commit_ids( ) ,
1042+ cl. concat_selected_commit_ids( ) . unwrap ( ) ,
10261043 Some ( String :: from(
10271044 "0000000000000000000000000000000000000002"
10281045 ) )
@@ -1037,35 +1054,37 @@ mod tests {
10371054 ..cl
10381055 } ;
10391056 assert_eq ! (
1040- cl. concat_selected_commit_ids( ) ,
1057+ cl. concat_selected_commit_ids( ) . unwrap ( ) ,
10411058 Some ( String :: from(
10421059 "0000000000000000000000000000000000000001" ,
10431060 ) )
10441061 ) ;
10451062 }
10461063
10471064 #[ test]
1065+ #[ ignore = "needs real repository to run test. Will be moved to revwalk module." ]
10481066 fn test_copy_commit_range_marked ( ) {
10491067 let cl = build_commit_list_with_some_commits ( ) ;
10501068 let cl = CommitList {
10511069 marked : build_marked_from_indices ( & cl, & [ 4 , 5 , 6 , 7 ] ) ,
10521070 ..cl
10531071 } ;
10541072 assert_eq ! (
1055- cl. concat_selected_commit_ids( ) ,
1073+ cl. concat_selected_commit_ids( ) . unwrap ( ) ,
10561074 Some ( String :: from( "0000000000000000000000000000000000000005^..0000000000000000000000000000000000000002" ) )
10571075 ) ;
10581076 }
10591077
10601078 #[ test]
1079+ #[ ignore = "needs real repository to run test. Will be moved to revwalk module." ]
10611080 fn test_copy_commit_random_marked ( ) {
10621081 let cl = build_commit_list_with_some_commits ( ) ;
10631082 let cl = CommitList {
10641083 marked : build_marked_from_indices ( & cl, & [ 4 , 7 ] ) ,
10651084 ..cl
10661085 } ;
10671086 assert_eq ! (
1068- cl. concat_selected_commit_ids( ) ,
1087+ cl. concat_selected_commit_ids( ) . unwrap ( ) ,
10691088 Some ( String :: from( concat!(
10701089 "0000000000000000000000000000000000000002 " ,
10711090 "0000000000000000000000000000000000000005"
0 commit comments