Resolve output from tree-way diff chunks

This commit is contained in:
Magnus Hoff 2017-11-17 15:32:45 +01:00
parent 34ca3e424d
commit dfefe78b2b
4 changed files with 206 additions and 19 deletions

8
src/merge/chunk.rs Normal file
View file

@ -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<Item>],
pub &'a [diff::Result<Item>]
);

View file

@ -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<Item>], &'a [diff::Result<Item>]);
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<Item>],
right: &'a [diff::Result<Item>],
}
impl<'a, Item> MergeIterator<'a, Item>
impl<'a, Item> ChunkIterator<'a, Item>
where
Item: 'a + Debug + PartialEq + Eq
{
fn new(left: &'a [diff::Result<Item>], right: &'a [diff::Result<Item>]) -> MergeIterator<'a, Item> {
MergeIterator { left, right }
pub fn new(left: &'a [diff::Result<Item>], right: &'a [diff::Result<Item>]) -> 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::<Vec<_>>();
let chunks = ChunkIterator::new(&oa, &ob).collect::<Vec<_>>();
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::<Vec<_>>();
let chunks = ChunkIterator::new(&oa, &ob).collect::<Vec<_>>();
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::<Vec<_>>();
let chunks = ChunkIterator::new(&oa, &ob).collect::<Vec<_>>();
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::<Vec<_>>();
let chunks = ChunkIterator::new(&oa, &ob).collect::<Vec<_>>();
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::<Vec<_>>();
let chunks = ChunkIterator::new(&oa, &ob).collect::<Vec<_>>();
assert_eq!(vec![
Chunk(&oa[0..6], &ob[0..6]),
], merge);
], chunks);
}
}

33
src/merge/mod.rs Normal file
View file

@ -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<Output<char>> {
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",
));
}
}

147
src/merge/output.rs Normal file
View file

@ -0,0 +1,147 @@
use std::fmt::Debug;
use diff;
use diff::Result::*;
use super::chunk::Chunk;
#[derive(Debug, PartialEq)]
pub enum Output<Item: Debug + PartialEq + Copy> {
Resolved(Vec<Item>),
Conflict(Vec<Item>, Vec<Item>, Vec<Item>),
}
fn choose_left<Item: Copy>(operations: &[diff::Result<Item>]) -> Vec<Item> {
operations
.iter()
.filter_map(|x| match x {
&Both(y, _) => Some(y),
&Left(y) => Some(y),
&Right(_) => None,
})
.collect()
}
fn choose_right<Item: Copy>(operations: &[diff::Result<Item>]) -> Vec<Item> {
operations
.iter()
.filter_map(|x| match x {
&Both(_, y) => Some(y),
&Left(_) => None,
&Right(y) => Some(y),
})
.collect()
}
fn no_change<Item>(operations: &[diff::Result<Item>]) -> bool {
operations
.iter()
.all(|x| match x {
&Both(..) => true,
_ => false,
})
}
pub fn resolve<'a, Item: 'a + Debug + PartialEq + Copy>(chunk: Chunk<'a, Item>) -> Output<Item> {
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::<i32>(Chunk(&[], &[]))
);
}
#[test]
fn same() {
assert_eq!(
Output::Resolved(vec![
1
]),
resolve::<i32>(Chunk(
&[Both(1, 1)],
&[Both(1, 1)]
))
);
}
#[test]
fn only_left() {
assert_eq!(
Output::Resolved(vec![
2
]),
resolve::<i32>(Chunk(
&[
Left(1),
Right(2)
],
&[]
))
);
}
#[test]
fn false_conflict() {
assert_eq!(
Output::Resolved(vec![
2
]),
resolve::<i32>(Chunk(
&[
Left(1),
Right(2)
],
&[
Left(1),
Right(2)
],
))
);
}
#[test]
fn real_conflict() {
assert_eq!(
Output::Conflict(
vec![2],
vec![1],
vec![3],
),
resolve::<i32>(Chunk(
&[
Left(1),
Right(2)
],
&[
Left(1),
Right(3)
],
))
);
}
}