#[derive(Debug, Default, Clone)] pub struct Patch { pub hunks: Vec, } impl Patch { pub fn parse_unified_diff(unified_diff: &str) -> Patch { let mut current_file = String::new(); let mut is_filename_inherited = false; let mut hunk = Hunk::default(); let mut patch = Patch::default(); let mut in_header = true; for line in unified_diff.lines() { if line.starts_with("--- ") || line.starts_with("+++") || line.starts_with("@@") { in_header = false; } if in_header { continue; } if line.starts_with("@@") { if !hunk.lines.is_empty() { patch.hunks.push(hunk); } hunk = Hunk::from_header(line, ¤t_file, is_filename_inherited); is_filename_inherited = true; } else if let Some(path) = line.strip_prefix("--- ") { is_filename_inherited = false; let path = path.trim().strip_prefix("a/").unwrap_or(path); if path != "/dev/null" { current_file = path.into(); } } else if let Some(path) = line.strip_prefix("+++ ") { is_filename_inherited = false; let path = path.trim().strip_prefix("b/").unwrap_or(path); if path != "/dev/null" { current_file = path.into(); } } else if let Some(line) = line.strip_prefix('+') { hunk.lines.push(PatchLine::Addition(line.to_string())); } else if let Some(line) = line.strip_prefix('-') { hunk.lines.push(PatchLine::Deletion(line.to_string())); } else if let Some(line) = line.strip_prefix(' ') { hunk.lines.push(PatchLine::Context(line.to_string())); } else { hunk.lines.push(PatchLine::Garbage(line.to_string())); } } if !hunk.lines.is_empty() { patch.hunks.push(hunk); } patch } } #[derive(Debug, Default, Clone)] pub struct Hunk { pub old_start: isize, pub new_start: isize, pub lines: Vec, pub filename: String, } impl Hunk { pub fn from_header(header: &str, filename: &str, _is_filename_inherited: bool) -> Self { let (old_start, _, new_start, _, _) = Self::parse_hunk_header(header); Self { old_start, new_start, lines: Vec::new(), filename: filename.to_string(), } } fn parse_hunk_header(line: &str) -> (isize, isize, isize, isize, String) { let header_part = line.trim_start_matches("@@").trim(); let parts: Vec<&str> = header_part.split_whitespace().collect(); if parts.len() < 2 { return (0, 0, 0, 0, String::new()); } let old_part = parts[0].trim_start_matches('-'); let new_part = parts[1].trim_start_matches('+'); let (old_start, old_count) = Hunk::parse_hunk_header_range(old_part); let (new_start, new_count) = Hunk::parse_hunk_header_range(new_part); let comment = if parts.len() > 2 { parts[2..] .join(" ") .trim_start_matches("@@") .trim() .to_string() } else { String::new() }; ( old_start as isize, old_count as isize, new_start as isize, new_count as isize, comment, ) } fn parse_hunk_header_range(part: &str) -> (usize, usize) { if let Some((start, count)) = part.split_once(',') { (start.parse().unwrap_or(0), count.parse().unwrap_or(0)) } else { (part.parse().unwrap_or(0), 1) } } } #[derive(Clone, Debug, Eq, PartialEq)] pub enum PatchLine { Context(String), Addition(String), Deletion(String), Garbage(String), }