Skip to content

Commit ad9b6dd

Browse files
authored
Merge pull request #26 from 231220075/main
add compute diff for code-blame.
2 parents 93b0968 + 1c42e47 commit ad9b6dd

File tree

1 file changed

+92
-1
lines changed

1 file changed

+92
-1
lines changed

src/diff.rs

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ pub struct DiffItem {
1818

1919
pub 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)]
2230
enum EditLine<'a> {
2331
// old_line, new_line, text
@@ -29,6 +37,49 @@ enum EditLine<'a> {
2937
}
3038

3139
impl 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)]
423479
mod 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

Comments
 (0)