#![allow(dead_code)]
use anyhow::Result;
#[derive(Debug, Default, Clone)]
pub struct DiffParseResult {
pub files: Vec<DiffFile>,
}
#[derive(Debug, Default, Clone)]
pub struct DiffFile {
pub path: String,
pub hunks: Vec<DiffHunk>,
}
#[derive(Debug, Default, Clone)]
pub struct DiffHunk {
pub header: String,
pub old_start: i32,
pub old_lines: i32,
pub new_start: i32,
pub new_lines: i32,
pub lines: Vec<DiffLine>,
}
#[derive(Debug, Default, Clone)]
pub struct DiffLine {
pub kind: DiffLineKind,
pub content: String,
pub old_lineno: Option<i32>,
pub new_lineno: Option<i32>,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum DiffLineKind {
#[default]
Context,
Add,
Delete,
HunkHeader,
}
pub fn parse_diff(raw: &str) -> Result<DiffParseResult> {
let mut files = Vec::new();
let mut current_file: Option<DiffFile> = None;
let mut current_hunk: Option<DiffHunk> = None;
let mut old_lineno = 0;
let mut new_lineno = 0;
for line in raw.lines() {
if line.starts_with("Binary files") {
continue;
} else if line.starts_with("diff --git ") {
if let Some(mut f) = current_file.take() {
if let Some(h) = current_hunk.take() {
f.hunks.push(h);
}
files.push(f);
}
let path = line
.split_whitespace()
.nth(3)
.unwrap_or("b/unknown")
.trim_start_matches("b/")
.to_string();
current_file = Some(DiffFile {
path,
hunks: Vec::new(),
});
old_lineno = 0;
new_lineno = 0;
} else if line.starts_with("@@") {
if let Some(ref mut f) = current_file {
if let Some(h) = current_hunk.take() {
f.hunks.push(h);
}
}
let header = line.to_string();
let parts: Vec<&str> = line.split_whitespace().collect();
let (mut old_start, mut old_count, mut new_start, mut new_count) = (0, 0, 0, 0);
if parts.len() >= 3 {
if let Some(old) = parts.get(1) {
let nums: Vec<&str> = old.trim_start_matches('-').split(',').collect();
old_start = nums.get(0).and_then(|n| n.parse::<i32>().ok()).unwrap_or(0);
old_count = nums.get(1).and_then(|n| n.parse::<i32>().ok()).unwrap_or(0);
}
if let Some(newn) = parts.get(2) {
let nums: Vec<&str> = newn.trim_start_matches('+').split(',').collect();
new_start = nums.get(0).and_then(|n| n.parse::<i32>().ok()).unwrap_or(0);
new_count = nums.get(1).and_then(|n| n.parse::<i32>().ok()).unwrap_or(0);
}
}
old_lineno = old_start;
new_lineno = new_start;
current_hunk = Some(DiffHunk {
header: header.clone(),
old_start,
old_lines: old_count,
new_start,
new_lines: new_count,
lines: vec![DiffLine {
kind: DiffLineKind::HunkHeader,
content: header,
old_lineno: None,
new_lineno: None,
}],
});
} else if line.starts_with('+') || line.starts_with('-') || line.starts_with(' ') {
if let Some(ref mut h) = current_hunk {
let (kind, old_no, new_no) = if line.starts_with('+') {
let ln = new_lineno;
new_lineno += 1;
(DiffLineKind::Add, None, Some(ln))
} else if line.starts_with('-') {
let ln = old_lineno;
old_lineno += 1;
(DiffLineKind::Delete, Some(ln), None)
} else {
let o = old_lineno;
let n = new_lineno;
old_lineno += 1;
new_lineno += 1;
(DiffLineKind::Context, Some(o), Some(n))
};
h.lines.push(DiffLine {
kind,
content: line.to_string(),
old_lineno: old_no,
new_lineno: new_no,
});
}
} else {
}
}
if let Some(mut f) = current_file {
if let Some(h) = current_hunk {
f.hunks.push(h);
}
files.push(f);
}
Ok(DiffParseResult { files })
}