Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gix-blame/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ gix-commitgraph = { version = "^0.30.0", path = "../gix-commitgraph" }
gix-revwalk = { version = "^0.23.0", path = "../gix-revwalk" }
gix-trace = { version = "^0.1.14", path = "../gix-trace" }
gix-date = { version = "^0.11.0", path = "../gix-date" }
gix-diff = { version = "^0.55.0", path = "../gix-diff", default-features = false, features = ["blob"] }
gix-diff = { version = "^0.55.0", path = "../gix-diff", default-features = false, features = ["blob", "blob-experimental"] }
gix-object = { version = "^0.52.0", path = "../gix-object" }
gix-hash = { version = "^0.20.0", path = "../gix-hash" }
gix-worktree = { version = "^0.44.0", path = "../gix-worktree", default-features = false, features = ["attributes"] }
Expand Down
105 changes: 44 additions & 61 deletions gix-blame/src/file/function.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::{num::NonZeroU32, ops::Range};
use std::num::NonZeroU32;

use gix_diff::{blob::intern::TokenSource, tree::Visit};
use gix_diff::{
blob::{intern::TokenSource, v2::Hunk},
tree::Visit,
};
use gix_hash::ObjectId;
use gix_object::{
bstr::{BStr, BString},
Expand Down Expand Up @@ -758,85 +761,65 @@ fn blob_changes(
diff_algorithm: gix_diff::blob::Algorithm,
stats: &mut Statistics,
) -> Result<Vec<Change>, Error> {
/// Record all [`Change`]s to learn about additions, deletions and unchanged portions of a *Source File*.
struct ChangeRecorder {
last_seen_after_end: u32,
hunks: Vec<Change>,
total_number_of_lines: u32,
}
resource_cache.set_resource(
previous_oid,
gix_object::tree::EntryKind::Blob,
previous_file_path,
gix_diff::blob::ResourceKind::OldOrSource,
&odb,
)?;
resource_cache.set_resource(
oid,
gix_object::tree::EntryKind::Blob,
file_path,
gix_diff::blob::ResourceKind::NewOrDestination,
&odb,
)?;

impl ChangeRecorder {
/// `total_number_of_lines` is used to fill in the last unchanged hunk if needed
/// so that the entire file is represented by [`Change`].
fn new(total_number_of_lines: u32) -> Self {
ChangeRecorder {
last_seen_after_end: 0,
hunks: Vec::new(),
total_number_of_lines,
}
}
}
let outcome = resource_cache.prepare_diff()?;
let input = gix_diff::blob::v2::InternedInput::new(
outcome.old.data.as_slice().unwrap_or_default(),
outcome.new.data.as_slice().unwrap_or_default(),
);

let diff_algorithm: gix_diff::blob::v2::Algorithm = match diff_algorithm {
gix_diff::blob::Algorithm::Histogram => gix_diff::blob::v2::Algorithm::Histogram,
gix_diff::blob::Algorithm::Myers => gix_diff::blob::v2::Algorithm::Myers,
gix_diff::blob::Algorithm::MyersMinimal => gix_diff::blob::v2::Algorithm::MyersMinimal,
};
let mut diff = gix_diff::blob::v2::Diff::compute(diff_algorithm, &input);
diff.postprocess_lines(&input);

impl gix_diff::blob::Sink for ChangeRecorder {
type Out = Vec<Change>;
let changes = diff
.hunks()
.fold((Vec::new(), 0), |(mut hunks, mut last_seen_after_end), hunk| {
let Hunk { before, after } = hunk;

fn process_change(&mut self, before: Range<u32>, after: Range<u32>) {
// This checks for unchanged hunks.
if after.start > self.last_seen_after_end {
self.hunks
.push(Change::Unchanged(self.last_seen_after_end..after.start));
if after.start > last_seen_after_end {
hunks.push(Change::Unchanged(last_seen_after_end..after.start));
}

match (!before.is_empty(), !after.is_empty()) {
(_, true) => {
self.hunks.push(Change::AddedOrReplaced(
hunks.push(Change::AddedOrReplaced(
after.start..after.end,
before.end - before.start,
));
}
(true, false) => {
self.hunks.push(Change::Deleted(after.start, before.end - before.start));
hunks.push(Change::Deleted(after.start, before.end - before.start));
}
(false, false) => unreachable!("BUG: imara-diff provided a non-change"),
}
self.last_seen_after_end = after.end;
}

fn finish(mut self) -> Self::Out {
if self.total_number_of_lines > self.last_seen_after_end {
self.hunks
.push(Change::Unchanged(self.last_seen_after_end..self.total_number_of_lines));
}
self.hunks
}
}

resource_cache.set_resource(
previous_oid,
gix_object::tree::EntryKind::Blob,
previous_file_path,
gix_diff::blob::ResourceKind::OldOrSource,
&odb,
)?;
resource_cache.set_resource(
oid,
gix_object::tree::EntryKind::Blob,
file_path,
gix_diff::blob::ResourceKind::NewOrDestination,
&odb,
)?;
last_seen_after_end = after.end;

let outcome = resource_cache.prepare_diff()?;
let input = gix_diff::blob::intern::InternedInput::new(
tokens_for_diffing(outcome.old.data.as_slice().unwrap_or_default()),
tokens_for_diffing(outcome.new.data.as_slice().unwrap_or_default()),
);
let number_of_lines_in_destination = input.after.len();
let change_recorder = ChangeRecorder::new(number_of_lines_in_destination as u32);
(hunks, last_seen_after_end)
});

let res = gix_diff::blob::diff(diff_algorithm, &input, change_recorder);
stats.blobs_diffed += 1;
Ok(res)
Ok(changes.0)
}

fn find_path_entry_in_commit(
Expand Down
5 changes: 3 additions & 2 deletions gix-blame/tests/blame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,11 +292,12 @@ mktest!(
3
);

/// As of 2024-09-24, these tests are expected to fail.
/// As of 2024-09-24, the Myers-related test was expected to fail. Both tests were initially
/// written when diffing was done by `imara-diff` 0.1. After updating to `imara-diff` 0.2 on
/// 2025-12-07, the Myers-related test started passing.
///
/// Context: https://github.com/Byron/gitoxide/pull/1453#issuecomment-2371013904
#[test]
#[should_panic = "empty-lines-myers"]
fn diff_disparity() {
for case in ["empty-lines-myers", "empty-lines-histogram"] {
let Fixture {
Expand Down
Loading