@@ -7,6 +7,9 @@ use gix_object::{
7
7
bstr:: { BStr , BString } ,
8
8
FindExt ,
9
9
} ;
10
+ use gix_traverse:: commit:: find;
11
+ use smallvec:: SmallVec ;
12
+ use std:: collections:: HashSet ;
10
13
use std:: num:: NonZeroU32 ;
11
14
use std:: ops:: Range ;
12
15
@@ -103,20 +106,115 @@ where
103
106
suspects: [ ( suspect, range_in_blamed_file) ] . into( ) ,
104
107
} ] ;
105
108
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
+
106
152
let mut out = Vec :: new ( ) ;
107
153
let mut diff_state = gix_diff:: tree:: State :: default ( ) ;
108
154
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 ( ) {
110
156
if hunks_to_blame. is_empty ( ) {
111
157
break ;
112
158
}
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
+
115
167
stats. commits_traversed += 1 ;
116
168
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
+
118
213
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
+ //
120
218
// I’m not entirely sure if this is correct yet. `suspect`, at this point, is the `id` of
121
219
// the last `item` that was yielded by `traverse`, so it makes sense to assign the
122
220
// remaining lines to it, even though we don’t explicitly check whether that is true
@@ -143,7 +241,7 @@ where
143
241
continue ;
144
242
} ;
145
243
146
- for ( pid, parent_id) in parent_ids. iter ( ) . enumerate ( ) {
244
+ for ( pid, ( parent_id, parent_commit_time ) ) in parent_ids. iter ( ) . enumerate ( ) {
147
245
if let Some ( parent_entry_id) =
148
246
find_path_entry_in_commit ( & odb, parent_id, file_path, & mut buf, & mut buf2, & mut stats) ?
149
247
{
@@ -153,17 +251,19 @@ where
153
251
}
154
252
if no_change_in_entry {
155
253
pass_blame_from_to ( suspect, * parent_id, & mut hunks_to_blame) ;
254
+ queue. insert ( * parent_commit_time, * parent_id) ;
156
255
continue ' outer;
157
256
}
158
257
}
159
258
}
160
259
161
260
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) ;
163
263
let changes_for_file_path = tree_diff_at_file_path (
164
264
& odb,
165
265
file_path,
166
- commit . id ,
266
+ suspect ,
167
267
parent_id,
168
268
& mut stats,
169
269
& mut diff_state,
0 commit comments