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;
|
||||||
use diff::Result::*;
|
use diff::Result::*;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
use super::chunk::Chunk;
|
||||||
struct Chunk<'a, Item: 'a + Debug + PartialEq + Eq>(&'a [diff::Result<Item>], &'a [diff::Result<Item>]);
|
|
||||||
|
|
||||||
struct MergeIterator<'a, Item>
|
pub struct ChunkIterator<'a, Item>
|
||||||
where
|
where
|
||||||
Item: 'a + Debug + PartialEq + Eq
|
Item: 'a + Debug + PartialEq
|
||||||
{
|
{
|
||||||
left: &'a [diff::Result<Item>],
|
left: &'a [diff::Result<Item>],
|
||||||
right: &'a [diff::Result<Item>],
|
right: &'a [diff::Result<Item>],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Item> MergeIterator<'a, Item>
|
impl<'a, Item> ChunkIterator<'a, Item>
|
||||||
where
|
where
|
||||||
Item: 'a + Debug + PartialEq + Eq
|
Item: 'a + Debug + PartialEq + Eq
|
||||||
{
|
{
|
||||||
fn new(left: &'a [diff::Result<Item>], right: &'a [diff::Result<Item>]) -> MergeIterator<'a, Item> {
|
pub fn new(left: &'a [diff::Result<Item>], right: &'a [diff::Result<Item>]) -> ChunkIterator<'a, Item> {
|
||||||
MergeIterator { left, right }
|
ChunkIterator { left, right }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Item> Iterator for MergeIterator<'a, Item>
|
impl<'a, Item> Iterator for ChunkIterator<'a, Item>
|
||||||
where
|
where
|
||||||
Item: 'a + Debug + PartialEq + Eq
|
Item: 'a + Debug + PartialEq + Copy
|
||||||
{
|
{
|
||||||
type Item = Chunk<'a, Item>;
|
type Item = Chunk<'a, Item>;
|
||||||
|
|
||||||
|
@ -93,7 +92,7 @@ mod test {
|
||||||
let oa = diff::chars(o, a);
|
let oa = diff::chars(o, a);
|
||||||
let ob = diff::chars(o, b);
|
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![
|
assert_eq!(vec![
|
||||||
Chunk(&oa[0.. 3], &ob[0.. 3]),
|
Chunk(&oa[0.. 3], &ob[0.. 3]),
|
||||||
|
@ -101,7 +100,7 @@ mod test {
|
||||||
Chunk(&oa[6.. 9], &ob[3.. 6]),
|
Chunk(&oa[6.. 9], &ob[3.. 6]),
|
||||||
Chunk(&oa[9.. 9], &ob[6.. 9]),
|
Chunk(&oa[9.. 9], &ob[6.. 9]),
|
||||||
Chunk(&oa[9..12], &ob[9..12]),
|
Chunk(&oa[9..12], &ob[9..12]),
|
||||||
], merge);
|
], chunks);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -113,12 +112,12 @@ mod test {
|
||||||
let oa = diff::chars(o, a);
|
let oa = diff::chars(o, a);
|
||||||
let ob = diff::chars(o, b);
|
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![
|
assert_eq!(vec![
|
||||||
Chunk(&oa[0.. 3], &ob[0.. 3]),
|
Chunk(&oa[0.. 3], &ob[0.. 3]),
|
||||||
Chunk(&oa[3.. 9], &ob[3.. 9]),
|
Chunk(&oa[3.. 9], &ob[3.. 9]),
|
||||||
Chunk(&oa[9..12], &ob[9..12]),
|
Chunk(&oa[9..12], &ob[9..12]),
|
||||||
], merge);
|
], chunks);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -130,11 +129,11 @@ mod test {
|
||||||
let oa = diff::chars(o, a);
|
let oa = diff::chars(o, a);
|
||||||
let ob = diff::chars(o, b);
|
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![
|
assert_eq!(vec![
|
||||||
Chunk(&oa[0..9], &ob[0.. 9]),
|
Chunk(&oa[0..9], &ob[0.. 9]),
|
||||||
Chunk(&oa[9..9], &ob[9..12]),
|
Chunk(&oa[9..9], &ob[9..12]),
|
||||||
], merge);
|
], chunks);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -146,11 +145,11 @@ mod test {
|
||||||
let oa = diff::chars(o, a);
|
let oa = diff::chars(o, a);
|
||||||
let ob = diff::chars(o, b);
|
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![
|
assert_eq!(vec![
|
||||||
Chunk(&oa[0..6], &ob[0.. 6]),
|
Chunk(&oa[0..6], &ob[0.. 6]),
|
||||||
Chunk(&oa[6..9], &ob[6..12]),
|
Chunk(&oa[6..9], &ob[6..12]),
|
||||||
], merge);
|
], chunks);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -162,9 +161,9 @@ mod test {
|
||||||
let oa = diff::chars(o, a);
|
let oa = diff::chars(o, a);
|
||||||
let ob = diff::chars(o, b);
|
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![
|
assert_eq!(vec![
|
||||||
Chunk(&oa[0..6], &ob[0..6]),
|
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