From dfefe78b2bf26a84cc42dae29190a0bb63e3b718 Mon Sep 17 00:00:00 2001 From: Magnus Hoff Date: Fri, 17 Nov 2017 15:32:45 +0100 Subject: [PATCH] Resolve output from tree-way diff chunks --- src/merge/chunk.rs | 8 ++ src/{merge.rs => merge/chunk_iterator.rs} | 37 +++--- src/merge/mod.rs | 33 +++++ src/merge/output.rs | 147 ++++++++++++++++++++++ 4 files changed, 206 insertions(+), 19 deletions(-) create mode 100644 src/merge/chunk.rs rename src/{merge.rs => merge/chunk_iterator.rs} (80%) create mode 100644 src/merge/mod.rs create mode 100644 src/merge/output.rs diff --git a/src/merge/chunk.rs b/src/merge/chunk.rs new file mode 100644 index 0000000..41dfcdf --- /dev/null +++ b/src/merge/chunk.rs @@ -0,0 +1,8 @@ +use std::fmt::Debug; +use diff; + +#[derive(Debug, PartialEq)] +pub struct Chunk<'a, Item: 'a + Debug + PartialEq + Copy>( + pub &'a [diff::Result], + pub &'a [diff::Result] +); diff --git a/src/merge.rs b/src/merge/chunk_iterator.rs similarity index 80% rename from src/merge.rs rename to src/merge/chunk_iterator.rs index 1dd0788..13f95e0 100644 --- a/src/merge.rs +++ b/src/merge/chunk_iterator.rs @@ -3,29 +3,28 @@ use std::fmt::Debug; use diff; use diff::Result::*; -#[derive(Debug, PartialEq)] -struct Chunk<'a, Item: 'a + Debug + PartialEq + Eq>(&'a [diff::Result], &'a [diff::Result]); +use super::chunk::Chunk; -struct MergeIterator<'a, Item> +pub struct ChunkIterator<'a, Item> where - Item: 'a + Debug + PartialEq + Eq + Item: 'a + Debug + PartialEq { left: &'a [diff::Result], right: &'a [diff::Result], } -impl<'a, Item> MergeIterator<'a, Item> +impl<'a, Item> ChunkIterator<'a, Item> where Item: 'a + Debug + PartialEq + Eq { - fn new(left: &'a [diff::Result], right: &'a [diff::Result]) -> MergeIterator<'a, Item> { - MergeIterator { left, right } + pub fn new(left: &'a [diff::Result], right: &'a [diff::Result]) -> ChunkIterator<'a, Item> { + ChunkIterator { left, right } } } -impl<'a, Item> Iterator for MergeIterator<'a, Item> +impl<'a, Item> Iterator for ChunkIterator<'a, Item> where - Item: 'a + Debug + PartialEq + Eq + Item: 'a + Debug + PartialEq + Copy { type Item = Chunk<'a, Item>; @@ -93,7 +92,7 @@ mod test { let oa = diff::chars(o, a); let ob = diff::chars(o, b); - let merge = MergeIterator::new(&oa, &ob).collect::>(); + let chunks = ChunkIterator::new(&oa, &ob).collect::>(); assert_eq!(vec![ Chunk(&oa[0.. 3], &ob[0.. 3]), @@ -101,7 +100,7 @@ mod test { Chunk(&oa[6.. 9], &ob[3.. 6]), Chunk(&oa[9.. 9], &ob[6.. 9]), Chunk(&oa[9..12], &ob[9..12]), - ], merge); + ], chunks); } #[test] @@ -113,12 +112,12 @@ mod test { let oa = diff::chars(o, a); let ob = diff::chars(o, b); - let merge = MergeIterator::new(&oa, &ob).collect::>(); + let chunks = ChunkIterator::new(&oa, &ob).collect::>(); assert_eq!(vec![ Chunk(&oa[0.. 3], &ob[0.. 3]), Chunk(&oa[3.. 9], &ob[3.. 9]), Chunk(&oa[9..12], &ob[9..12]), - ], merge); + ], chunks); } #[test] @@ -130,11 +129,11 @@ mod test { let oa = diff::chars(o, a); let ob = diff::chars(o, b); - let merge = MergeIterator::new(&oa, &ob).collect::>(); + let chunks = ChunkIterator::new(&oa, &ob).collect::>(); assert_eq!(vec![ Chunk(&oa[0..9], &ob[0.. 9]), Chunk(&oa[9..9], &ob[9..12]), - ], merge); + ], chunks); } #[test] @@ -146,11 +145,11 @@ mod test { let oa = diff::chars(o, a); let ob = diff::chars(o, b); - let merge = MergeIterator::new(&oa, &ob).collect::>(); + let chunks = ChunkIterator::new(&oa, &ob).collect::>(); assert_eq!(vec![ Chunk(&oa[0..6], &ob[0.. 6]), Chunk(&oa[6..9], &ob[6..12]), - ], merge); + ], chunks); } #[test] @@ -162,9 +161,9 @@ mod test { let oa = diff::chars(o, a); let ob = diff::chars(o, b); - let merge = MergeIterator::new(&oa, &ob).collect::>(); + let chunks = ChunkIterator::new(&oa, &ob).collect::>(); assert_eq!(vec![ Chunk(&oa[0..6], &ob[0..6]), - ], merge); + ], chunks); } } diff --git a/src/merge/mod.rs b/src/merge/mod.rs new file mode 100644 index 0000000..3484d3c --- /dev/null +++ b/src/merge/mod.rs @@ -0,0 +1,33 @@ +mod chunk_iterator; +mod chunk; +mod output; + +#[cfg(test)] +mod test { + use diff; + use super::output::*; + use super::output::Output::*; + + fn merge_chars(a: &str, o: &str, b: &str) -> Vec> { + let oa = diff::chars(o, a); + let ob = diff::chars(o, b); + + let merge = super::chunk_iterator::ChunkIterator::new(&oa, &ob); + merge.map(|x| resolve(x)).collect() + } + + #[test] + fn simple_case() { + assert_eq!(vec![ + Resolved("aaa".chars().collect()), + Resolved("xxx".chars().collect()), + Resolved("bbb".chars().collect()), + Resolved("yyy".chars().collect()), + Resolved("ccc".chars().collect()), + ], merge_chars( + "aaaxxxbbbccc", + "aaabbbccc", + "aaabbbyyyccc", + )); + } +} diff --git a/src/merge/output.rs b/src/merge/output.rs new file mode 100644 index 0000000..30e278b --- /dev/null +++ b/src/merge/output.rs @@ -0,0 +1,147 @@ +use std::fmt::Debug; + +use diff; +use diff::Result::*; + +use super::chunk::Chunk; + +#[derive(Debug, PartialEq)] +pub enum Output { + Resolved(Vec), + Conflict(Vec, Vec, Vec), +} + +fn choose_left(operations: &[diff::Result]) -> Vec { + operations + .iter() + .filter_map(|x| match x { + &Both(y, _) => Some(y), + &Left(y) => Some(y), + &Right(_) => None, + }) + .collect() +} + +fn choose_right(operations: &[diff::Result]) -> Vec { + operations + .iter() + .filter_map(|x| match x { + &Both(_, y) => Some(y), + &Left(_) => None, + &Right(y) => Some(y), + }) + .collect() +} + +fn no_change(operations: &[diff::Result]) -> bool { + operations + .iter() + .all(|x| match x { + &Both(..) => true, + _ => false, + }) +} + +pub fn resolve<'a, Item: 'a + Debug + PartialEq + Copy>(chunk: Chunk<'a, Item>) -> Output { + if chunk.0 == chunk.1 { + // Either nothing changed or both sides made the same change + return Output::Resolved(choose_right(chunk.0)); + } + + if no_change(chunk.0) { + return Output::Resolved(choose_right(chunk.1)); + } + + if no_change(chunk.1) { + return Output::Resolved(choose_right(chunk.0)); + } + + return Output::Conflict( + choose_right(chunk.0), + choose_left(chunk.0), + choose_right(chunk.1), + ); +} + +#[cfg(test)] +mod test { + use diff::Result::*; + use super::*; + + #[test] + fn empty() { + assert_eq!( + Output::Resolved(vec![]), + resolve::(Chunk(&[], &[])) + ); + } + + #[test] + fn same() { + assert_eq!( + Output::Resolved(vec![ + 1 + ]), + resolve::(Chunk( + &[Both(1, 1)], + &[Both(1, 1)] + )) + ); + } + + #[test] + fn only_left() { + assert_eq!( + Output::Resolved(vec![ + 2 + ]), + resolve::(Chunk( + &[ + Left(1), + Right(2) + ], + &[] + )) + ); + } + + #[test] + fn false_conflict() { + assert_eq!( + Output::Resolved(vec![ + 2 + ]), + resolve::(Chunk( + &[ + Left(1), + Right(2) + ], + &[ + Left(1), + Right(2) + ], + )) + ); + } + + #[test] + fn real_conflict() { + assert_eq!( + Output::Conflict( + vec![2], + vec![1], + vec![3], + ), + resolve::(Chunk( + &[ + Left(1), + Right(2) + ], + &[ + Left(1), + Right(3) + ], + )) + ); + } +}