Skip to content

Commit

Permalink
lib merge: add DiffOfMerge type
Browse files Browse the repository at this point in the history
This represents a diff of two merges, up to reordering and cancelling
out of terms. It can also be thought of as a stack of `n` diffs.

This type is not very suitable for presenting to the user (since it
loses information they might want to see), but is very natural from the
perspective of conflict theory.
  • Loading branch information
ilyagr committed Aug 21, 2024
1 parent 7435cc7 commit ebbdf63
Showing 1 changed file with 93 additions and 12 deletions.
105 changes: 93 additions & 12 deletions lib/src/merge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,70 @@ fn describe_conflict_term(value: &TreeValue) -> String {
}
}

// TODO(ilyagr): DiffOfMerges might not need to be public, might need renaming

/// A sequence of diffs forming a "conflicted diff"
///
/// Conceptually, this is very similar to a merge, except it has the same number
/// of positive terms (corresponding to "adds" in a merge) and negative terms
/// (corresponding to "removes" in a merge).
///
/// This can represent a diff of two merges, with corresponding terms cancelling
/// out. The adds of the negative (left-hand side) merge will then become a
/// negative term of the DiffOfMerges. However, in this case, it is not
/// remembered which term comes from which merge. By itself, a sequence of diffs
/// resulting from a diff of two merges cannot tell the difference between the
/// a) right-hand merge adding a diff (X-Y) to the left-hand side, or b) the
/// right-hand merge subtracting the opposite diff (Y-X) that was present on the
/// left-hand side.
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub struct DiffOfMerges<T> {
/// Alternates between positive and negative terms, starting with positive.
values: Vec<T>,
}
impl<T> DiffOfMerges<T> {
/// Creates a `DiffOfMerges` from the given values, in which positive and
/// negative terms alternate.
pub fn from_vec(values: impl Into<Vec<T>>) -> Self {
let values = values.into();
assert!(
values.len() & 1 == 0,
"must have equal numbers of adds and removes"
);
DiffOfMerges { values }
}

/// Creates a new merge object from the given removes and adds.
// TODO: Rename to `from_negatives_positives` (?)
pub fn from_removes_adds(
removes: impl IntoIterator<Item = T>,
adds: impl IntoIterator<Item = T>,
) -> Self {
let removes = removes.into_iter();
let adds = adds.into_iter();
let mut values = Vec::with_capacity(removes.size_hint().0 * 2);
for diff in removes.zip_longest(adds) {
let (remove, add) = diff
.both()
.expect("must have the same number of adds and removes");
values.extend([remove, add]);
}
DiffOfMerges { values }
}

/// Simplify the diff by removing equal positive and negative terms.
///
/// The positive terms are allowed to be reordered freely, as are the
/// negative terms. The pairing of them into diffs is **not** preserved.
pub fn simplify(mut self) -> Self
where
T: PartialEq + Clone,
{
self.values = simplify_internal(self.values);
self
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -697,6 +761,10 @@ mod tests {
Merge::from_removes_adds(removes.to_vec(), adds.to_vec())
}

fn d<T: Clone>(removes: &[T], adds: &[T]) -> DiffOfMerges<T> {
DiffOfMerges::from_removes_adds(removes.to_vec(), adds.to_vec())
}

#[test]
fn test_trivial_merge() {
assert_eq!(trivial_merge(&[], &[0]), Some(&0));
Expand Down Expand Up @@ -832,7 +900,7 @@ mod tests {
assert_eq!(get_simplified_mapping(&[1, 0, 0, 0, 0]), vec![0]);
assert_eq!(get_simplified_mapping(&[1, 0, 0, 0, 1]), vec![0, 3, 4],);
assert_eq!(get_simplified_mapping(&[1, 0, 0, 0, 2]), vec![0, 3, 4],);
assert_eq!(get_simplified_mapping(&[1, 0, 1, 0, 0]), vec![0, 3, 2],);
assert_eq!(get_simplified_mapping(&[1, 0, 1, 0, 0]), vec![2, 3, 0],);
assert_eq!(
get_simplified_mapping(&[1, 0, 1, 0, 1]),
vec![0, 1, 2, 3, 4],
Expand All @@ -841,7 +909,7 @@ mod tests {
get_simplified_mapping(&[1, 0, 1, 0, 2]),
vec![0, 1, 2, 3, 4],
);
assert_eq!(get_simplified_mapping(&[1, 0, 2, 0, 0]), vec![0, 3, 2],);
assert_eq!(get_simplified_mapping(&[1, 0, 2, 0, 0]), vec![2, 3, 0],);
assert_eq!(
get_simplified_mapping(&[1, 0, 2, 0, 1]),
vec![0, 1, 2, 3, 4],
Expand All @@ -864,16 +932,16 @@ mod tests {
assert_eq!(get_simplified_mapping(&[0, 0, 2, 1, 1]), vec![2]);
assert_eq!(get_simplified_mapping(&[0, 0, 2, 1, 2]), vec![2, 3, 4],);
assert_eq!(get_simplified_mapping(&[0, 0, 2, 1, 3]), vec![2, 3, 4],);
assert_eq!(get_simplified_mapping(&[1, 0, 0, 1, 0]), vec![2]);
assert_eq!(get_simplified_mapping(&[1, 0, 0, 1, 0]), vec![4]);
assert_eq!(get_simplified_mapping(&[1, 0, 0, 1, 1]), vec![4]);
assert_eq!(get_simplified_mapping(&[1, 0, 0, 1, 2]), vec![4]);
assert_eq!(get_simplified_mapping(&[1, 0, 1, 1, 0]), vec![2]);
assert_eq!(get_simplified_mapping(&[1, 0, 1, 1, 1]), vec![4, 1, 2],);
assert_eq!(get_simplified_mapping(&[1, 0, 1, 1, 2]), vec![4, 1, 2],);
assert_eq!(get_simplified_mapping(&[1, 0, 1, 1, 1]), vec![2, 1, 4],);
assert_eq!(get_simplified_mapping(&[1, 0, 1, 1, 2]), vec![2, 1, 4],);
assert_eq!(get_simplified_mapping(&[1, 0, 2, 1, 0]), vec![2]);
assert_eq!(get_simplified_mapping(&[1, 0, 2, 1, 1]), vec![4, 1, 2],);
assert_eq!(get_simplified_mapping(&[1, 0, 2, 1, 2]), vec![4, 1, 2],);
assert_eq!(get_simplified_mapping(&[1, 0, 2, 1, 3]), vec![4, 1, 2],);
assert_eq!(get_simplified_mapping(&[1, 0, 2, 1, 1]), vec![2, 1, 4],);
assert_eq!(get_simplified_mapping(&[1, 0, 2, 1, 2]), vec![2, 1, 4],);
assert_eq!(get_simplified_mapping(&[1, 0, 2, 1, 3]), vec![2, 1, 4],);
assert_eq!(get_simplified_mapping(&[2, 0, 0, 1, 0]), vec![0, 3, 4],);
assert_eq!(get_simplified_mapping(&[2, 0, 0, 1, 1]), vec![0]);
assert_eq!(get_simplified_mapping(&[2, 0, 0, 1, 2]), vec![0, 3, 4],);
Expand All @@ -882,7 +950,7 @@ mod tests {
assert_eq!(get_simplified_mapping(&[2, 0, 1, 1, 1]), vec![0, 1, 4],);
assert_eq!(get_simplified_mapping(&[2, 0, 1, 1, 2]), vec![0, 1, 4],);
assert_eq!(get_simplified_mapping(&[2, 0, 1, 1, 3]), vec![0, 1, 4],);
assert_eq!(get_simplified_mapping(&[2, 0, 2, 1, 0]), vec![0, 3, 2],);
assert_eq!(get_simplified_mapping(&[2, 0, 2, 1, 0]), vec![2, 3, 0],);
assert_eq!(get_simplified_mapping(&[2, 0, 2, 1, 1]), vec![0, 1, 2],);
assert_eq!(
get_simplified_mapping(&[2, 0, 2, 1, 2]),
Expand All @@ -892,7 +960,7 @@ mod tests {
get_simplified_mapping(&[2, 0, 2, 1, 3]),
vec![0, 1, 2, 3, 4],
);
assert_eq!(get_simplified_mapping(&[2, 0, 3, 1, 0]), vec![0, 3, 2],);
assert_eq!(get_simplified_mapping(&[2, 0, 3, 1, 0]), vec![2, 3, 0],);
assert_eq!(get_simplified_mapping(&[2, 0, 3, 1, 1]), vec![0, 1, 2],);
assert_eq!(
get_simplified_mapping(&[2, 0, 3, 1, 2]),
Expand All @@ -908,12 +976,12 @@ mod tests {
);
assert_eq!(
get_simplified_mapping(&[3, 0, 4, 1, 5, 2, 0]),
vec![0, 3, 4, 5, 2],
vec![2, 3, 4, 5, 0],
);
}

#[test]
fn test_simplify() {
fn test_simplify_merge() {
// 1-way merge
assert_eq!(c(&[], &[0]).simplify(), c(&[], &[0]));
// 3-way merge
Expand Down Expand Up @@ -981,6 +1049,19 @@ mod tests {
);
}

#[test]
fn test_simplify_diff_of_merges() {
assert_eq!(d::<usize>(&[], &[]).simplify(), d(&[], &[]));
assert_eq!(d(&[0], &[0]).simplify(), d(&[], &[]));
assert_eq!(d(&[1], &[0]).simplify(), d(&[1], &[0]));
assert_eq!(d(&[0, 0], &[0, 0]).simplify(), d(&[], &[]));
assert_eq!(d(&[0, 1], &[1, 0]).simplify(), d(&[], &[]));
assert_eq!(d(&[0, 1], &[0, 1]).simplify(), d(&[], &[]));
assert_eq!(d(&[1, 1], &[0, 1]).simplify(), d(&[1], &[0]));
assert_eq!(d(&[1, 0], &[0, 2]).simplify(), d(&[1], &[2]));
assert_eq!(d(&[1, 0], &[3, 2]).simplify(), d(&[1, 0], &[3, 2]));
}

#[test]
fn test_update_from_simplified() {
// 1-way merge
Expand Down

0 comments on commit ebbdf63

Please sign in to comment.