Skip to content

Commit 81297cf

Browse files
committed
feat!: add debug_track_path and blame_path
1 parent 298f22e commit 81297cf

File tree

4 files changed

+102
-6
lines changed

4 files changed

+102
-6
lines changed

gix-blame/src/file/function.rs

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use gix_traverse::commit::find as find_commit;
1010
use smallvec::SmallVec;
1111

1212
use 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,22 @@ 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+
};
178+
blame_path.push(blame_path_entry);
179+
}
180+
159181
break 'outer;
160182
}
161183
}
@@ -271,12 +293,23 @@ pub fn file(
271293
};
272294

273295
match modification {
274-
TreeDiffChange::Addition => {
296+
TreeDiffChange::Addition { id } => {
275297
if more_than_one_parent {
276298
// Do nothing under the assumption that this always (or almost always)
277299
// implies that the file comes from a different parent, compared to which
278300
// it was modified, not added.
279301
} else if unblamed_to_out_is_done(&mut hunks_to_blame, &mut out, suspect) {
302+
if let Some(ref mut blame_path) = blame_path {
303+
let blame_path_entry = BlamePathEntry {
304+
source_file_path: current_file_path.clone(),
305+
previous_source_file_path: None,
306+
commit_id: suspect,
307+
blob_id: id,
308+
previous_blob_id: ObjectId::null(gix_hash::Kind::Sha1),
309+
};
310+
blame_path.push(blame_path_entry);
311+
}
312+
280313
break 'outer;
281314
}
282315
}
@@ -295,6 +328,16 @@ pub fn file(
295328
&mut stats,
296329
)?;
297330
hunks_to_blame = process_changes(hunks_to_blame, changes, suspect, parent_id);
331+
if let Some(ref mut blame_path) = blame_path {
332+
let blame_path_entry = BlamePathEntry {
333+
source_file_path: current_file_path.clone(),
334+
previous_source_file_path: Some(current_file_path.clone()),
335+
commit_id: suspect,
336+
blob_id: id,
337+
previous_blob_id: previous_id,
338+
};
339+
blame_path.push(blame_path_entry);
340+
}
298341
}
299342
TreeDiffChange::Rewrite {
300343
source_location,
@@ -313,9 +356,26 @@ pub fn file(
313356
)?;
314357
hunks_to_blame = process_changes(hunks_to_blame, changes, suspect, parent_id);
315358

359+
let mut has_blame_been_passed = false;
360+
316361
for hunk in hunks_to_blame.iter_mut() {
317362
if hunk.has_suspect(&parent_id) {
318363
hunk.source_file_name = Some(source_location.clone());
364+
365+
has_blame_been_passed = true;
366+
}
367+
}
368+
369+
if has_blame_been_passed {
370+
if let Some(ref mut blame_path) = blame_path {
371+
let blame_path_entry = BlamePathEntry {
372+
source_file_path: current_file_path.clone(),
373+
previous_source_file_path: Some(source_location.clone()),
374+
commit_id: suspect,
375+
blob_id: id,
376+
previous_blob_id: source_id,
377+
};
378+
blame_path.push(blame_path_entry);
319379
}
320380
}
321381
}
@@ -351,6 +411,7 @@ pub fn file(
351411
entries: coalesce_blame_entries(out),
352412
blob: blamed_file_blob,
353413
statistics: stats,
414+
blame_path,
354415
})
355416
}
356417

@@ -435,7 +496,9 @@ fn coalesce_blame_entries(lines_blamed: Vec<BlameEntry>) -> Vec<BlameEntry> {
435496
/// The union of [`gix_diff::tree::recorder::Change`] and [`gix_diff::tree_with_rewrites::Change`],
436497
/// keeping only the blame-relevant information.
437498
enum TreeDiffChange {
438-
Addition,
499+
Addition {
500+
id: ObjectId,
501+
},
439502
Deletion,
440503
Modification {
441504
previous_id: ObjectId,
@@ -453,7 +516,7 @@ impl From<gix_diff::tree::recorder::Change> for TreeDiffChange {
453516
use gix_diff::tree::recorder::Change;
454517

455518
match value {
456-
Change::Addition { .. } => Self::Addition,
519+
Change::Addition { oid, .. } => Self::Addition { id: oid },
457520
Change::Deletion { .. } => Self::Deletion,
458521
Change::Modification { previous_oid, oid, .. } => Self::Modification {
459522
previous_id: previous_oid,
@@ -468,7 +531,7 @@ impl From<gix_diff::tree_with_rewrites::Change> for TreeDiffChange {
468531
use gix_diff::tree_with_rewrites::Change;
469532

470533
match value {
471-
Change::Addition { .. } => Self::Addition,
534+
Change::Addition { id, .. } => Self::Addition { id },
472535
Change::Deletion { .. } => Self::Deletion,
473536
Change::Modification { previous_id, id, .. } => Self::Modification { previous_id, id },
474537
Change::Rewrite {

gix-blame/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
mod error;
1818
pub use error::Error;
1919
mod types;
20-
pub use types::{BlameEntry, BlameRanges, Options, Outcome, Statistics};
20+
pub use types::{BlameEntry, BlamePathEntry, BlameRanges, Options, Outcome, Statistics};
2121

2222
mod file;
2323
pub use file::function::file;

gix-blame/src/types.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,30 @@ pub struct Options {
149149
pub since: Option<gix_date::Time>,
150150
/// Determine if rename tracking should be performed, and how.
151151
pub rewrites: Option<gix_diff::Rewrites>,
152+
/// Collect debug information whenever there's a diff or rename that affects the outcome of a
153+
/// blame.
154+
pub debug_track_path: bool,
155+
}
156+
157+
/// Represents a change during history traversal for blame. It is supposed to capture enough
158+
/// information to allow reconstruction of the way a blame was performed, i. e. the path the
159+
/// history traversal, combined with repeated diffing of two subsequent states in this history, has
160+
/// taken.
161+
///
162+
/// This is intended for debugging purposes.
163+
#[derive(Clone, Debug)]
164+
pub struct BlamePathEntry {
165+
/// The path to the *Source File* in the blob after the change.
166+
pub source_file_path: BString,
167+
/// The path to the *Source File* in the blob before the change. Allows
168+
/// detection of renames. `None` for root commits.
169+
pub previous_source_file_path: Option<BString>,
170+
/// The commit id associated with the state after the change.
171+
pub commit_id: ObjectId,
172+
/// The blob id associated with the state after the change.
173+
pub blob_id: ObjectId,
174+
/// The blob id associated with the state before the change.
175+
pub previous_blob_id: ObjectId,
152176
}
153177

154178
/// The outcome of [`file()`](crate::file()).
@@ -161,6 +185,8 @@ pub struct Outcome {
161185
pub blob: Vec<u8>,
162186
/// Additional information about the amount of work performed to produce the blame.
163187
pub statistics: Statistics,
188+
/// Contains a log of all changes that affected the outcome of this blame.
189+
pub blame_path: Option<Vec<BlamePathEntry>>,
164190
}
165191

166192
/// Additional information about the performed operations.

gix-blame/tests/blame.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ macro_rules! mktest {
232232
range: BlameRanges::default(),
233233
since: None,
234234
rewrites: Some(gix_diff::Rewrites::default()),
235+
debug_track_path: false,
235236
},
236237
)?
237238
.entries;
@@ -317,6 +318,7 @@ fn diff_disparity() {
317318
range: BlameRanges::default(),
318319
since: None,
319320
rewrites: Some(gix_diff::Rewrites::default()),
321+
debug_track_path: false,
320322
},
321323
)
322324
.unwrap()
@@ -352,6 +354,7 @@ fn since() -> gix_testtools::Result {
352354
range: BlameRanges::default(),
353355
since: Some(gix_date::parse("2025-01-31", None)?),
354356
rewrites: Some(gix_diff::Rewrites::default()),
357+
debug_track_path: false,
355358
},
356359
)?
357360
.entries;
@@ -391,6 +394,7 @@ mod blame_ranges {
391394
range: BlameRanges::from_range(1..=2),
392395
since: None,
393396
rewrites: Some(gix_diff::Rewrites::default()),
397+
debug_track_path: false,
394398
},
395399
)?
396400
.entries;
@@ -431,6 +435,7 @@ mod blame_ranges {
431435
range: ranges,
432436
since: None,
433437
rewrites: None,
438+
debug_track_path: false,
434439
},
435440
)?
436441
.entries;
@@ -471,6 +476,7 @@ mod blame_ranges {
471476
range: ranges,
472477
since: None,
473478
rewrites: None,
479+
debug_track_path: false,
474480
},
475481
)?
476482
.entries;
@@ -516,6 +522,7 @@ mod rename_tracking {
516522
range: BlameRanges::default(),
517523
since: None,
518524
rewrites: Some(gix_diff::Rewrites::default()),
525+
debug_track_path: false,
519526
},
520527
)?
521528
.entries;

0 commit comments

Comments
 (0)