diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/calculation.rs | 106 | ||||
| -rw-r--r-- | src/diff.rs | 26 | ||||
| -rw-r--r-- | src/lib.rs | 2 | ||||
| -rw-r--r-- | src/object.rs | 2 |
4 files changed, 131 insertions, 5 deletions
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<Error: Clone>( Ok(conflicting_nodes) } -pub struct FileContent(Vec<FileContentSpan>); +pub struct FileContent(pub Vec<FileContentSpan>); -pub struct FileContentSpan(Vec<FileContentNode>); +pub enum FileContentSpan { + Conflicted(ConflictedFileContentSpan), + Unconflicted(UnconflictedFileContentSpan), +} + +pub struct ConflictedFileContentSpan(pub Vec<(String, UnconflictedFileContentSpan)>); + +pub struct UnconflictedFileContentSpan(pub Vec<FileContentNode>); +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct FileContentNode { + id: SpanNodeId, patch: PatchId, content: Vec<u8>, } +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<Error: Clone>( file: &FileInfo, active_patches: &HashSet<PatchId>, get_patch: &impl Fn(&PatchId) -> Result<Patch, Error>, + get_patch_label: impl Fn(&PatchId) -> String, ) -> Result<FileContent, Error> { let mut output = Vec::new(); let mut completed_nodes = HashSet::new(); @@ -209,13 +231,15 @@ pub fn file_content<Error: Clone>( 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<Error: Clone>( } } - 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<Error: Clone>( 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<FileContentMap> { + 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 }) +} diff --git a/src/diff.rs b/src/diff.rs new file mode 100644 index 0000000..0d11400 --- /dev/null +++ b/src/diff.rs @@ -0,0 +1,26 @@ +use std::ops::Range; + +use imara_diff::{Algorithm, Diff, Hunk, InternedInput}; + +pub enum DiffSpan { + Insertion(Range<u32>), + Deletion(Range<u32>), +} + +pub fn diff(before: &[u8], after: &[u8]) -> Vec<DiffSpan> { + let input = InternedInput::new(before, after); + let diff = Diff::compute(Algorithm::Histogram, &input); + + let hunks: Vec<Hunk> = diff.hunks().collect(); + let mut spans = Vec::with_capacity(hunks.len()); + for hunk in hunks { + if !hunk.before.is_empty() { + spans.push(DiffSpan::Deletion(hunk.before)); + } + if !hunk.after.is_empty() { + spans.push(DiffSpan::Insertion(hunk.after)); + } + } + + spans +} @@ -1,5 +1,6 @@ #![warn(clippy::pedantic)] #![warn(clippy::nursery)] +#![allow(clippy::redundant_else)] use std::collections::HashMap; use std::fs::{File, Metadata}; @@ -9,6 +10,7 @@ use std::time::Instant; use serde::{Deserialize, Serialize}; mod calculation; +mod diff; mod object; mod workarea; diff --git a/src/object.rs b/src/object.rs index e303900..68eec89 100644 --- a/src/object.rs +++ b/src/object.rs @@ -1,8 +1,8 @@ use std::io::{Read, Write}; use std::path::{Path, PathBuf}; -use serde::de::DeserializeOwned; use serde::Serialize; +use serde::de::DeserializeOwned; use crate::{FileInfo, Id, Patch}; |
