Skip to content

Commit ed9c437

Browse files
committed
Add feature flag blob-experimental to gix-blame
1 parent 2c88262 commit ed9c437

File tree

3 files changed

+124
-9
lines changed

3 files changed

+124
-9
lines changed

gix-blame/Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,17 @@ authors = ["Christoph Rüßler <[email protected]>", "Sebastian Thi
1010
edition = "2021"
1111
rust-version = "1.82"
1212

13+
[features]
14+
## An experimental use of the v0.2 branch of `imara-diff` to allow trying it out, and for writing tests against it more easily.
15+
## We will decide later how it should actually be exposed.
16+
blob-experimental = ["gix-diff/blob-experimental"]
17+
1318
[dependencies]
1419
gix-commitgraph = { version = "^0.30.0", path = "../gix-commitgraph" }
1520
gix-revwalk = { version = "^0.23.0", path = "../gix-revwalk" }
1621
gix-trace = { version = "^0.1.14", path = "../gix-trace" }
1722
gix-date = { version = "^0.11.0", path = "../gix-date" }
18-
gix-diff = { version = "^0.55.0", path = "../gix-diff", default-features = false, features = ["blob", "blob-experimental"] }
23+
gix-diff = { version = "^0.55.0", path = "../gix-diff", default-features = false, features = ["blob"] }
1924
gix-object = { version = "^0.52.0", path = "../gix-object" }
2025
gix-hash = { version = "^0.20.0", path = "../gix-hash" }
2126
gix-worktree = { version = "^0.44.0", path = "../gix-worktree", default-features = false, features = ["attributes"] }

gix-blame/src/file/function.rs

Lines changed: 100 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use std::num::NonZeroU32;
22

3-
use gix_diff::{
4-
blob::{intern::TokenSource, v2::Hunk},
5-
tree::Visit,
6-
};
3+
#[cfg(feature = "blob-experimental")]
4+
use gix_diff::blob::v2::Hunk;
5+
6+
use gix_diff::{blob::intern::TokenSource, tree::Visit};
77
use gix_hash::ObjectId;
88
use gix_object::{
99
bstr::{BStr, BString},
@@ -751,6 +751,102 @@ fn tree_diff_with_rewrites_at_file_path(
751751
}
752752

753753
#[allow(clippy::too_many_arguments)]
754+
#[cfg(not(feature = "blob-experimental"))]
755+
fn blob_changes(
756+
odb: impl gix_object::Find + gix_object::FindHeader,
757+
resource_cache: &mut gix_diff::blob::Platform,
758+
oid: ObjectId,
759+
previous_oid: ObjectId,
760+
file_path: &BStr,
761+
previous_file_path: &BStr,
762+
diff_algorithm: gix_diff::blob::Algorithm,
763+
stats: &mut Statistics,
764+
) -> Result<Vec<Change>, Error> {
765+
/// Record all [`Change`]s to learn about additions, deletions and unchanged portions of a *Source File*.
766+
struct ChangeRecorder {
767+
last_seen_after_end: u32,
768+
hunks: Vec<Change>,
769+
total_number_of_lines: u32,
770+
}
771+
772+
impl ChangeRecorder {
773+
/// `total_number_of_lines` is used to fill in the last unchanged hunk if needed
774+
/// so that the entire file is represented by [`Change`].
775+
fn new(total_number_of_lines: u32) -> Self {
776+
ChangeRecorder {
777+
last_seen_after_end: 0,
778+
hunks: Vec::new(),
779+
total_number_of_lines,
780+
}
781+
}
782+
}
783+
784+
use std::ops::Range;
785+
786+
impl gix_diff::blob::Sink for ChangeRecorder {
787+
type Out = Vec<Change>;
788+
789+
fn process_change(&mut self, before: Range<u32>, after: Range<u32>) {
790+
// This checks for unchanged hunks.
791+
if after.start > self.last_seen_after_end {
792+
self.hunks
793+
.push(Change::Unchanged(self.last_seen_after_end..after.start));
794+
}
795+
796+
match (!before.is_empty(), !after.is_empty()) {
797+
(_, true) => {
798+
self.hunks.push(Change::AddedOrReplaced(
799+
after.start..after.end,
800+
before.end - before.start,
801+
));
802+
}
803+
(true, false) => {
804+
self.hunks.push(Change::Deleted(after.start, before.end - before.start));
805+
}
806+
(false, false) => unreachable!("BUG: imara-diff provided a non-change"),
807+
}
808+
self.last_seen_after_end = after.end;
809+
}
810+
811+
fn finish(mut self) -> Self::Out {
812+
if self.total_number_of_lines > self.last_seen_after_end {
813+
self.hunks
814+
.push(Change::Unchanged(self.last_seen_after_end..self.total_number_of_lines));
815+
}
816+
self.hunks
817+
}
818+
}
819+
820+
resource_cache.set_resource(
821+
previous_oid,
822+
gix_object::tree::EntryKind::Blob,
823+
previous_file_path,
824+
gix_diff::blob::ResourceKind::OldOrSource,
825+
&odb,
826+
)?;
827+
resource_cache.set_resource(
828+
oid,
829+
gix_object::tree::EntryKind::Blob,
830+
file_path,
831+
gix_diff::blob::ResourceKind::NewOrDestination,
832+
&odb,
833+
)?;
834+
835+
let outcome = resource_cache.prepare_diff()?;
836+
let input = gix_diff::blob::intern::InternedInput::new(
837+
tokens_for_diffing(outcome.old.data.as_slice().unwrap_or_default()),
838+
tokens_for_diffing(outcome.new.data.as_slice().unwrap_or_default()),
839+
);
840+
let number_of_lines_in_destination = input.after.len();
841+
let change_recorder = ChangeRecorder::new(number_of_lines_in_destination as u32);
842+
843+
let res = gix_diff::blob::diff(diff_algorithm, &input, change_recorder);
844+
stats.blobs_diffed += 1;
845+
Ok(res)
846+
}
847+
848+
#[allow(clippy::too_many_arguments)]
849+
#[cfg(feature = "blob-experimental")]
754850
fn blob_changes(
755851
odb: impl gix_object::Find + gix_object::FindHeader,
756852
resource_cache: &mut gix_diff::blob::Platform,

gix-blame/tests/blame.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -292,13 +292,27 @@ mktest!(
292292
3
293293
);
294294

295-
/// As of 2024-09-24, the Myers-related test was expected to fail. Both tests were initially
296-
/// written when diffing was done by `imara-diff` 0.1. After updating to `imara-diff` 0.2 on
297-
/// 2025-12-07, the Myers-related test started passing.
295+
/// As of 2024-09-24, the Myers-related test is expected to fail. Both tests use `imara-diff` 0.1
296+
/// under the hood.
298297
///
299298
/// Context: https://github.com/Byron/gitoxide/pull/1453#issuecomment-2371013904
300299
#[test]
301-
fn diff_disparity() {
300+
#[should_panic = "empty-lines-myers"]
301+
#[cfg(not(feature = "blob-experimental"))]
302+
fn diff_disparity_imara_diff_v1() {
303+
diff_disparity_base()
304+
}
305+
306+
/// As of 2025-12-07, both tests are expected to pass. They use `imara-diff` 0.2 under the hood.
307+
///
308+
/// Context: https://github.com/Byron/gitoxide/pull/1453#issuecomment-2371013904
309+
#[test]
310+
#[cfg(feature = "blob-experimental")]
311+
fn diff_disparity_imara_diff_v2() {
312+
diff_disparity_base()
313+
}
314+
315+
fn diff_disparity_base() {
302316
for case in ["empty-lines-myers", "empty-lines-histogram"] {
303317
let Fixture {
304318
odb,

0 commit comments

Comments
 (0)