From 84105e10d0ce93052cdce677e8472bdcc3c388db Mon Sep 17 00:00:00 2001 From: Micha White Date: Sat, 4 Oct 2025 19:07:22 -0400 Subject: Basic diffing --- src/calculation.rs | 106 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 102 insertions(+), 4 deletions(-) (limited to 'src/calculation.rs') diff --git a/src/calculation.rs b/src/calculation.rs index e65e692..64b0f1e 100644 --- a/src/calculation.rs +++ b/src/calculation.rs @@ -1,4 +1,5 @@ use std::collections::{HashSet, VecDeque}; +use std::io::Write; use std::path::PathBuf; use crate::{FileInfo, FilenameOperation, Patch, PatchId, SpanNode, SpanNodeId}; @@ -160,19 +161,40 @@ pub fn conflicting_nodes( Ok(conflicting_nodes) } -pub struct FileContent(Vec); +pub struct FileContent(pub Vec); -pub struct FileContentSpan(Vec); +pub enum FileContentSpan { + Conflicted(ConflictedFileContentSpan), + Unconflicted(UnconflictedFileContentSpan), +} + +pub struct ConflictedFileContentSpan(pub Vec<(String, UnconflictedFileContentSpan)>); + +pub struct UnconflictedFileContentSpan(pub Vec); +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct FileContentNode { + id: SpanNodeId, patch: PatchId, content: Vec, } +fn mergable( + a: &[FileContentNode], + b: &ConflictedFileContentSpan, + get_patch_label: impl Fn(&PatchId) -> String, +) -> bool { + a.iter().all(|node| { + let label_a = get_patch_label(&node.patch); + b.0.iter().any(|(label_b, _)| &label_a == label_b) + }) +} + pub fn file_content( file: &FileInfo, active_patches: &HashSet, get_patch: &impl Fn(&PatchId) -> Result, + get_patch_label: impl Fn(&PatchId) -> String, ) -> Result { let mut output = Vec::new(); let mut completed_nodes = HashSet::new(); @@ -209,13 +231,15 @@ pub fn file_content( let span_contents = Vec::from(&patch.contents[content_start..content_end]); conflicting_nodes.push(FileContentNode { - patch: node.span.patch.clone(), + id: node_id.clone(), + patch: patch.id.clone(), content: span_contents, }); } if !relevant_patches.deletions.is_empty() { conflicting_nodes.push(FileContentNode { + id: node_id.clone(), patch: node.span.patch.clone(), content: Vec::new(), }); @@ -227,7 +251,43 @@ pub fn file_content( } } - output.push(FileContentSpan(conflicting_nodes)); + #[allow(clippy::collapsible_else_if)] + if conflicting_nodes.len() == 1 { + if let Some(FileContentSpan::Unconflicted(node)) = output.last_mut() { + node.0.push(conflicting_nodes[0].clone()); + } else { + output.push(FileContentSpan::Unconflicted(UnconflictedFileContentSpan( + vec![conflicting_nodes[0].clone()], + ))); + } + } else { + if let Some(FileContentSpan::Conflicted(span)) = output.last_mut() + && mergable(&conflicting_nodes, span, &get_patch_label) + { + 'outer: for node_a in conflicting_nodes { + for (label_b, span_b) in &mut span.0 { + let label_a = get_patch_label(&node_a.patch); + if &label_a == label_b { + span_b.0.push(node_a); + continue 'outer; + } + } + } + } else { + output.push(FileContentSpan::Conflicted(ConflictedFileContentSpan( + conflicting_nodes + .iter() + .map(|node| { + ( + get_patch_label(&node.patch), + UnconflictedFileContentSpan(vec![(node.clone())]), + ) + }) + .collect(), + ))); + } + } + conflicting_nodes = Vec::new(); (queue, queue_next) = (queue_next, queue); @@ -236,3 +296,41 @@ pub fn file_content( Ok(FileContent(output)) } + +pub struct FileContentMap { + nodes: Vec<(usize, SpanNodeId)>, +} + +pub fn write_file_content( + writer: &mut impl Write, + labelled_file_content: &FileContent, +) -> std::io::Result { + let mut nodes = Vec::new(); + let mut index = 0; + for span in &labelled_file_content.0 { + match span { + FileContentSpan::Unconflicted(span) => { + for node in &span.0 { + nodes.push((index, node.id.clone())); + index += node.content.len(); + writer.write_all(&node.content)?; + } + } + FileContentSpan::Conflicted(span) => { + for (label, span) in &span.0 { + let prefix = format!("======= {label}\n"); + writer.write_all(prefix.as_bytes())?; + index += prefix.len(); + + for node in &span.0 { + nodes.push((index, node.id.clone())); + writer.write_all(&node.content)?; + index += node.content.len(); + } + } + } + } + } + + Ok(FileContentMap { nodes }) +} -- cgit v1.2.3