@@ -10,7 +10,7 @@ use gix_traverse::commit::find as find_commit;
1010use smallvec:: SmallVec ;
1111
1212use super :: { process_changes, Change , UnblamedHunk } ;
13- use crate :: { BlameEntry , Error , Options , Outcome , Statistics } ;
13+ use crate :: { types :: BlamePathEntry , BlameEntry , Error , Options , Outcome , Statistics } ;
1414
1515/// Produce a list of consecutive [`BlameEntry`] instances to indicate in which commits the ranges of the file
1616/// at `suspect:<file_path>` originated in.
@@ -115,6 +115,12 @@ pub fn file(
115115 let mut out = Vec :: new ( ) ;
116116 let mut diff_state = gix_diff:: tree:: State :: default ( ) ;
117117 let mut previous_entry: Option < ( ObjectId , ObjectId ) > = None ;
118+ let mut blame_path = if options. debug_track_path {
119+ Some ( Vec :: new ( ) )
120+ } else {
121+ None
122+ } ;
123+
118124 ' outer: while let Some ( suspect) = queue. pop_value ( ) {
119125 stats. commits_traversed += 1 ;
120126 if hunks_to_blame. is_empty ( ) {
@@ -156,6 +162,23 @@ pub fn file(
156162 // true here. We could perhaps use diff-tree-to-tree to compare `suspect` against
157163 // an empty tree to validate this assumption.
158164 if unblamed_to_out_is_done ( & mut hunks_to_blame, & mut out, suspect) {
165+ if let Some ( ref mut blame_path) = blame_path {
166+ let entry = previous_entry
167+ . take ( )
168+ . filter ( |( id, _) | * id == suspect)
169+ . map ( |( _, entry) | entry) ;
170+
171+ let blame_path_entry = BlamePathEntry {
172+ source_file_path : current_file_path. clone ( ) ,
173+ previous_source_file_path : None ,
174+ commit_id : suspect,
175+ blob_id : entry. unwrap_or ( ObjectId :: null ( gix_hash:: Kind :: Sha1 ) ) ,
176+ previous_blob_id : ObjectId :: null ( gix_hash:: Kind :: Sha1 ) ,
177+ parent_index : 0 ,
178+ } ;
179+ blame_path. push ( blame_path_entry) ;
180+ }
181+
159182 break ' outer;
160183 }
161184 }
@@ -241,13 +264,13 @@ pub fn file(
241264 }
242265
243266 let more_than_one_parent = parent_ids. len ( ) > 1 ;
244- for ( parent_id, parent_commit_time) in parent_ids {
245- queue. insert ( parent_commit_time, parent_id) ;
267+ for ( index , ( parent_id, parent_commit_time) ) in parent_ids. iter ( ) . enumerate ( ) {
268+ queue. insert ( * parent_commit_time, * parent_id) ;
246269 let changes_for_file_path = tree_diff_at_file_path (
247270 & odb,
248271 current_file_path. as_ref ( ) ,
249272 suspect,
250- parent_id,
273+ * parent_id,
251274 cache. as_ref ( ) ,
252275 & mut stats,
253276 & mut diff_state,
@@ -262,21 +285,33 @@ pub fn file(
262285 // None of the changes affected the file we’re currently blaming.
263286 // Copy blame to parent.
264287 for unblamed_hunk in & mut hunks_to_blame {
265- unblamed_hunk. clone_blame ( suspect, parent_id) ;
288+ unblamed_hunk. clone_blame ( suspect, * parent_id) ;
266289 }
267290 } else {
268- pass_blame_from_to ( suspect, parent_id, & mut hunks_to_blame) ;
291+ pass_blame_from_to ( suspect, * parent_id, & mut hunks_to_blame) ;
269292 }
270293 continue ;
271294 } ;
272295
273296 match modification {
274- TreeDiffChange :: Addition => {
297+ TreeDiffChange :: Addition { id } => {
275298 if more_than_one_parent {
276299 // Do nothing under the assumption that this always (or almost always)
277300 // implies that the file comes from a different parent, compared to which
278301 // it was modified, not added.
279302 } else if unblamed_to_out_is_done ( & mut hunks_to_blame, & mut out, suspect) {
303+ if let Some ( ref mut blame_path) = blame_path {
304+ let blame_path_entry = BlamePathEntry {
305+ source_file_path : current_file_path. clone ( ) ,
306+ previous_source_file_path : None ,
307+ commit_id : suspect,
308+ blob_id : id,
309+ previous_blob_id : ObjectId :: null ( gix_hash:: Kind :: Sha1 ) ,
310+ parent_index : index,
311+ } ;
312+ blame_path. push ( blame_path_entry) ;
313+ }
314+
280315 break ' outer;
281316 }
282317 }
@@ -294,7 +329,22 @@ pub fn file(
294329 options. diff_algorithm ,
295330 & mut stats,
296331 ) ?;
297- hunks_to_blame = process_changes ( hunks_to_blame, changes, suspect, parent_id) ;
332+ hunks_to_blame = process_changes ( hunks_to_blame, changes. clone ( ) , suspect, * parent_id) ;
333+ if let Some ( ref mut blame_path) = blame_path {
334+ let has_blame_been_passed = hunks_to_blame. iter ( ) . any ( |hunk| hunk. has_suspect ( parent_id) ) ;
335+
336+ if has_blame_been_passed {
337+ let blame_path_entry = BlamePathEntry {
338+ source_file_path : current_file_path. clone ( ) ,
339+ previous_source_file_path : Some ( current_file_path. clone ( ) ) ,
340+ commit_id : suspect,
341+ blob_id : id,
342+ previous_blob_id : previous_id,
343+ parent_index : index,
344+ } ;
345+ blame_path. push ( blame_path_entry) ;
346+ }
347+ }
298348 }
299349 TreeDiffChange :: Rewrite {
300350 source_location,
@@ -311,11 +361,29 @@ pub fn file(
311361 options. diff_algorithm ,
312362 & mut stats,
313363 ) ?;
314- hunks_to_blame = process_changes ( hunks_to_blame, changes, suspect, parent_id) ;
364+ hunks_to_blame = process_changes ( hunks_to_blame, changes, suspect, * parent_id) ;
365+
366+ let mut has_blame_been_passed = false ;
315367
316368 for hunk in hunks_to_blame. iter_mut ( ) {
317- if hunk. has_suspect ( & parent_id) {
369+ if hunk. has_suspect ( parent_id) {
318370 hunk. source_file_name = Some ( source_location. clone ( ) ) ;
371+
372+ has_blame_been_passed = true ;
373+ }
374+ }
375+
376+ if has_blame_been_passed {
377+ if let Some ( ref mut blame_path) = blame_path {
378+ let blame_path_entry = BlamePathEntry {
379+ source_file_path : current_file_path. clone ( ) ,
380+ previous_source_file_path : Some ( source_location. clone ( ) ) ,
381+ commit_id : suspect,
382+ blob_id : id,
383+ previous_blob_id : source_id,
384+ parent_index : index,
385+ } ;
386+ blame_path. push ( blame_path_entry) ;
319387 }
320388 }
321389 }
@@ -351,6 +419,7 @@ pub fn file(
351419 entries : coalesce_blame_entries ( out) ,
352420 blob : blamed_file_blob,
353421 statistics : stats,
422+ blame_path,
354423 } )
355424}
356425
@@ -435,7 +504,9 @@ fn coalesce_blame_entries(lines_blamed: Vec<BlameEntry>) -> Vec<BlameEntry> {
435504/// The union of [`gix_diff::tree::recorder::Change`] and [`gix_diff::tree_with_rewrites::Change`],
436505/// keeping only the blame-relevant information.
437506enum TreeDiffChange {
438- Addition ,
507+ Addition {
508+ id : ObjectId ,
509+ } ,
439510 Deletion ,
440511 Modification {
441512 previous_id : ObjectId ,
@@ -453,7 +524,7 @@ impl From<gix_diff::tree::recorder::Change> for TreeDiffChange {
453524 use gix_diff:: tree:: recorder:: Change ;
454525
455526 match value {
456- Change :: Addition { .. } => Self :: Addition ,
527+ Change :: Addition { oid , .. } => Self :: Addition { id : oid } ,
457528 Change :: Deletion { .. } => Self :: Deletion ,
458529 Change :: Modification { previous_oid, oid, .. } => Self :: Modification {
459530 previous_id : previous_oid,
@@ -468,7 +539,7 @@ impl From<gix_diff::tree_with_rewrites::Change> for TreeDiffChange {
468539 use gix_diff:: tree_with_rewrites:: Change ;
469540
470541 match value {
471- Change :: Addition { .. } => Self :: Addition ,
542+ Change :: Addition { id , .. } => Self :: Addition { id } ,
472543 Change :: Deletion { .. } => Self :: Deletion ,
473544 Change :: Modification { previous_id, id, .. } => Self :: Modification { previous_id, id } ,
474545 Change :: Rewrite {
0 commit comments