Resolve output from tree-way diff chunks
This commit is contained in:
parent
34ca3e424d
commit
dfefe78b2b
4 changed files with 206 additions and 19 deletions
8
src/merge/chunk.rs
Normal file
8
src/merge/chunk.rs
Normal 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>]
|
||||
);
|
|
@ -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
33
src/merge/mod.rs
Normal 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
147
src/merge/output.rs
Normal 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)
|
||||
],
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue