@@ -7,6 +7,9 @@ use gix_object::{
77 bstr:: { BStr , BString } ,
88 FindExt ,
99} ;
10+ use gix_traverse:: commit:: find;
11+ use smallvec:: SmallVec ;
12+ use std:: collections:: HashSet ;
1013use std:: num:: NonZeroU32 ;
1114use std:: ops:: Range ;
1215
@@ -103,20 +106,115 @@ where
103106 suspects: [ ( suspect, range_in_blamed_file) ] . into( ) ,
104107 } ] ;
105108
109+ // TODO
110+ // Get `cache` as an argument to `file`.
111+ let cache: Option < gix_commitgraph:: Graph > = None ;
112+
113+ let mut buf = Vec :: new ( ) ;
114+ let commit = find ( cache. as_ref ( ) , & odb, & suspect, & mut buf) ?;
115+
116+ // TODO
117+ // This is a simplified version of `GenAndCommitTime` in
118+ // `gix-traverse/src/commit/topo/iter.rs`. There, generation is also part of the key. It’s
119+ // possible we need generation, but I have too little context to know.
120+ type CommitTime = i64 ;
121+
122+ let mut queue: gix_revwalk:: PriorityQueue < CommitTime , ObjectId > = gix_revwalk:: PriorityQueue :: new ( ) ;
123+ let mut seen: HashSet < ObjectId > = HashSet :: new ( ) ;
124+
125+ // TODO
126+ // This is a simplified version of `gen_and_commit_time` in
127+ // `gix-traverse/src/commit/topo/iter.rs`. It can probably be extracted.
128+ let commit_time = match commit {
129+ gix_traverse:: commit:: Either :: CommitRefIter ( commit_ref_iter) => {
130+ let mut commit_time = 0 ;
131+ for token in commit_ref_iter {
132+ use gix_object:: commit:: ref_iter:: Token as T ;
133+ match token {
134+ Ok ( T :: Tree { .. } ) => continue ,
135+ Ok ( T :: Parent { .. } ) => continue ,
136+ Ok ( T :: Author { .. } ) => continue ,
137+ Ok ( T :: Committer { signature } ) => {
138+ commit_time = signature. time . seconds ;
139+ break ;
140+ }
141+ Ok ( _unused_token) => break ,
142+ Err ( _err) => todo ! ( ) ,
143+ }
144+ }
145+ commit_time
146+ }
147+ gix_traverse:: commit:: Either :: CachedCommit ( commit) => commit. committer_timestamp ( ) as i64 ,
148+ } ;
149+
150+ queue. insert ( commit_time, suspect) ;
151+
106152 let mut out = Vec :: new ( ) ;
107153 let mut diff_state = gix_diff:: tree:: State :: default ( ) ;
108154 let mut previous_entry: Option < ( ObjectId , ObjectId ) > = None ;
109- ' outer: while let Some ( item ) = traverse . next ( ) {
155+ ' outer: while let Some ( suspect ) = queue . pop_value ( ) {
110156 if hunks_to_blame. is_empty ( ) {
111157 break ;
112158 }
113- let commit = item. map_err ( |err| Error :: Traverse ( err. into ( ) ) ) ?;
114- let suspect = commit. id ;
159+
160+ let was_inserted = seen. insert ( suspect) ;
161+
162+ if !was_inserted {
163+ // We have already visited `suspect` and can continue with the next one.
164+ continue ' outer;
165+ }
166+
115167 stats. commits_traversed += 1 ;
116168
117- let parent_ids = commit. parent_ids ;
169+ let commit = find ( cache. as_ref ( ) , & odb, & suspect, & mut buf) ?;
170+
171+ type ParentIds = SmallVec < [ ( gix_hash:: ObjectId , i64 ) ; 2 ] > ;
172+ let mut parent_ids: ParentIds = Default :: default ( ) ;
173+
174+ // TODO
175+ // This is a simplified version of `collect_parents` in
176+ // `gix-traverse/src/commit/topo/iter.rs`. It can probably be extracted.
177+ match commit {
178+ gix_traverse:: commit:: Either :: CachedCommit ( commit) => {
179+ let cache = cache
180+ . as_ref ( )
181+ . expect ( "find returned a cached commit, so we expect cache to be present" ) ;
182+ for parent_id in commit. iter_parents ( ) {
183+ match parent_id {
184+ Ok ( pos) => {
185+ let parent = cache. commit_at ( pos) ;
186+
187+ parent_ids. push ( ( parent. id ( ) . to_owned ( ) , parent. committer_timestamp ( ) as i64 ) ) ;
188+ }
189+ Err ( _) => todo ! ( ) ,
190+ }
191+ }
192+ }
193+ gix_traverse:: commit:: Either :: CommitRefIter ( commit_ref_iter) => {
194+ for token in commit_ref_iter {
195+ match token {
196+ Ok ( gix_object:: commit:: ref_iter:: Token :: Tree { .. } ) => continue ,
197+ Ok ( gix_object:: commit:: ref_iter:: Token :: Parent { id } ) => {
198+ let mut buf = Vec :: new ( ) ;
199+ let parent = odb. find_commit_iter ( id. as_ref ( ) , & mut buf) . ok ( ) ;
200+ let parent_commit_time = parent
201+ . and_then ( |parent| parent. committer ( ) . ok ( ) . map ( |committer| committer. time . seconds ) )
202+ . unwrap_or_default ( ) ;
203+
204+ parent_ids. push ( ( id, parent_commit_time) ) ;
205+ }
206+ Ok ( _unused_token) => break ,
207+ Err ( _err) => todo ! ( ) ,
208+ }
209+ }
210+ }
211+ } ;
212+
118213 if parent_ids. is_empty ( ) {
119- if traverse. peek ( ) . is_none ( ) {
214+ if queue. is_empty ( ) {
215+ // TODO
216+ // Adapt comment. Also all other comments that mention `traverse`.
217+ //
120218 // I’m not entirely sure if this is correct yet. `suspect`, at this point, is the `id` of
121219 // the last `item` that was yielded by `traverse`, so it makes sense to assign the
122220 // remaining lines to it, even though we don’t explicitly check whether that is true
@@ -143,7 +241,7 @@ where
143241 continue ;
144242 } ;
145243
146- for ( pid, parent_id) in parent_ids. iter ( ) . enumerate ( ) {
244+ for ( pid, ( parent_id, parent_commit_time ) ) in parent_ids. iter ( ) . enumerate ( ) {
147245 if let Some ( parent_entry_id) =
148246 find_path_entry_in_commit ( & odb, parent_id, file_path, & mut buf, & mut buf2, & mut stats) ?
149247 {
@@ -153,17 +251,19 @@ where
153251 }
154252 if no_change_in_entry {
155253 pass_blame_from_to ( suspect, * parent_id, & mut hunks_to_blame) ;
254+ queue. insert ( * parent_commit_time, * parent_id) ;
156255 continue ' outer;
157256 }
158257 }
159258 }
160259
161260 let more_than_one_parent = parent_ids. len ( ) > 1 ;
162- for parent_id in parent_ids {
261+ for ( parent_id, parent_commit_time) in parent_ids {
262+ queue. insert ( parent_commit_time, parent_id) ;
163263 let changes_for_file_path = tree_diff_at_file_path (
164264 & odb,
165265 file_path,
166- commit . id ,
266+ suspect ,
167267 parent_id,
168268 & mut stats,
169269 & mut diff_state,
0 commit comments