summaryrefslogtreecommitdiff
path: root/src/calculation.rs
diff options
context:
space:
mode:
authorMicha White <botahamec@outlook.com>2025-10-04 19:07:22 -0400
committerMicha White <botahamec@outlook.com>2025-10-04 19:07:22 -0400
commit84105e10d0ce93052cdce677e8472bdcc3c388db (patch)
tree4fff3ee866aff26c79c9cf3ec1bacba9cd78535f /src/calculation.rs
parent1dd18cdb4c9e537993cef42dd6bb7c040b9ca232 (diff)
Basic diffing
Diffstat (limited to 'src/calculation.rs')
-rw-r--r--src/calculation.rs106
1 files changed, 102 insertions, 4 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 })
+}