@@ -18,6 +18,14 @@ pub struct DiffItem {
1818
1919pub struct Diff ;
2020
21+ /// Diff line operation types primarily used by blame computation to map parent/child lines.
22+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
23+ pub enum DiffOperation {
24+ Insert { line : usize , content : String } ,
25+ Delete { line : usize } ,
26+ Equal { old_line : usize , new_line : usize } ,
27+ }
28+
2129#[ derive( Debug , Clone , Copy ) ]
2230enum EditLine < ' a > {
2331 // old_line, new_line, text
@@ -29,6 +37,49 @@ enum EditLine<'a> {
2937}
3038
3139impl Diff {
40+ fn compute_line_operations ( old_lines : & [ String ] , new_lines : & [ String ] ) -> Vec < DiffOperation > {
41+ if old_lines. is_empty ( ) && new_lines. is_empty ( ) {
42+ return Vec :: new ( ) ;
43+ }
44+
45+ let old_refs: Vec < & str > = old_lines. iter ( ) . map ( |s| s. as_str ( ) ) . collect ( ) ;
46+ let new_refs: Vec < & str > = new_lines. iter ( ) . map ( |s| s. as_str ( ) ) . collect ( ) ;
47+
48+ let diff = TextDiff :: configure ( )
49+ . algorithm ( Algorithm :: Myers )
50+ . diff_slices ( & old_refs, & new_refs) ;
51+
52+ let mut operations = Vec :: with_capacity ( old_lines. len ( ) + new_lines. len ( ) ) ;
53+ let mut old_line_no = 1usize ;
54+ let mut new_line_no = 1usize ;
55+
56+ for change in diff. iter_all_changes ( ) {
57+ match change. tag ( ) {
58+ ChangeTag :: Equal => {
59+ operations. push ( DiffOperation :: Equal {
60+ old_line : old_line_no,
61+ new_line : new_line_no,
62+ } ) ;
63+ old_line_no += 1 ;
64+ new_line_no += 1 ;
65+ }
66+ ChangeTag :: Delete => {
67+ operations. push ( DiffOperation :: Delete { line : old_line_no } ) ;
68+ old_line_no += 1 ;
69+ }
70+ ChangeTag :: Insert => {
71+ operations. push ( DiffOperation :: Insert {
72+ line : new_line_no,
73+ content : change. value ( ) . to_string ( ) ,
74+ } ) ;
75+ new_line_no += 1 ;
76+ }
77+ }
78+ }
79+
80+ operations
81+ }
82+
3283 const MAX_DIFF_LINES : usize = 10_000 ; // safety cap for pathological inputs
3384 const LARGE_FILE_MARKER : & ' static str = "<LargeFile>" ;
3485 const LARGE_FILE_END : & ' static str = "</LargeFile>" ;
@@ -419,9 +470,14 @@ impl Diff {
419470 }
420471}
421472
473+ /// Compute Myers diff operations for blame/line-mapping scenarios.
474+ pub fn compute_diff ( old_lines : & [ String ] , new_lines : & [ String ] ) -> Vec < DiffOperation > {
475+ Diff :: compute_line_operations ( old_lines, new_lines)
476+ }
477+
422478#[ cfg( test) ]
423479mod tests {
424- use super :: Diff ;
480+ use super :: { Diff , DiffOperation , compute_diff } ;
425481 use crate :: hash:: SHA1 ;
426482 use std:: collections:: HashMap ;
427483 use std:: fs;
@@ -611,4 +667,39 @@ mod tests {
611667 assert_eq ! ( ours_del, git_del, "deleted lines differ from git output" ) ;
612668 assert_eq ! ( ours_ins, git_ins, "inserted lines differ from git output" ) ;
613669 }
670+
671+ #[ test]
672+ fn compute_diff_operations_basic_mapping ( ) {
673+ let old_lines = vec ! [ "a" . to_string( ) , "b" . to_string( ) , "c" . to_string( ) ] ;
674+ let new_lines = vec ! [
675+ "a" . to_string( ) ,
676+ "B" . to_string( ) ,
677+ "c" . to_string( ) ,
678+ "d" . to_string( ) ,
679+ ] ;
680+
681+ let ops = compute_diff ( & old_lines, & new_lines) ;
682+
683+ let expected = vec ! [
684+ DiffOperation :: Equal {
685+ old_line: 1 ,
686+ new_line: 1 ,
687+ } ,
688+ DiffOperation :: Delete { line: 2 } ,
689+ DiffOperation :: Insert {
690+ line: 2 ,
691+ content: "B" . to_string( ) ,
692+ } ,
693+ DiffOperation :: Equal {
694+ old_line: 3 ,
695+ new_line: 3 ,
696+ } ,
697+ DiffOperation :: Insert {
698+ line: 4 ,
699+ content: "d" . to_string( ) ,
700+ } ,
701+ ] ;
702+
703+ assert_eq ! ( ops, expected) ;
704+ }
614705}
0 commit comments