2023-05-13 18:16:55 +00:00
|
|
|
// Copyright 2023 The Jujutsu Authors
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
2023-08-24 04:23:37 +00:00
|
|
|
//! Generic algorithms for working with merged values, plus specializations for
|
|
|
|
//! some common types of merged values.
|
2023-07-10 15:17:00 +00:00
|
|
|
|
2023-08-06 16:21:35 +00:00
|
|
|
use std::borrow::Borrow;
|
2023-05-13 18:16:55 +00:00
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::hash::Hash;
|
2023-08-06 16:21:35 +00:00
|
|
|
use std::io::Write;
|
2023-08-11 05:06:38 +00:00
|
|
|
use std::iter::zip;
|
2023-08-06 16:21:35 +00:00
|
|
|
use std::sync::Arc;
|
2023-05-13 18:16:55 +00:00
|
|
|
|
|
|
|
use itertools::Itertools;
|
|
|
|
|
2023-08-10 06:46:25 +00:00
|
|
|
use crate::backend;
|
|
|
|
use crate::backend::{BackendError, FileId, ObjectId, TreeId, TreeValue};
|
2023-08-06 16:21:35 +00:00
|
|
|
use crate::content_hash::ContentHash;
|
|
|
|
use crate::repo_path::RepoPath;
|
|
|
|
use crate::store::Store;
|
|
|
|
use crate::tree::Tree;
|
|
|
|
|
2023-05-13 18:16:55 +00:00
|
|
|
/// Attempt to resolve trivial conflicts between the inputs. There must be
|
|
|
|
/// exactly one more adds than removes.
|
2023-05-30 04:46:38 +00:00
|
|
|
pub fn trivial_merge<'a, T>(removes: &'a [T], adds: &'a [T]) -> Option<&'a T>
|
2023-05-13 18:16:55 +00:00
|
|
|
where
|
2023-05-30 04:46:38 +00:00
|
|
|
T: Eq + Hash,
|
2023-05-13 18:16:55 +00:00
|
|
|
{
|
|
|
|
assert_eq!(
|
|
|
|
adds.len(),
|
|
|
|
removes.len() + 1,
|
|
|
|
"trivial_merge() requires exactly one more adds than removes"
|
|
|
|
);
|
|
|
|
|
2023-06-22 08:53:57 +00:00
|
|
|
// Optimize the common cases of 3-way merge and 1-way (non-)merge
|
|
|
|
if adds.len() == 1 {
|
|
|
|
return Some(&adds[0]);
|
|
|
|
} else if adds.len() == 2 {
|
2023-05-15 05:24:06 +00:00
|
|
|
return if adds[0] == adds[1] {
|
2023-05-30 04:46:38 +00:00
|
|
|
Some(&adds[0])
|
2023-05-15 05:24:06 +00:00
|
|
|
} else if adds[0] == removes[0] {
|
2023-05-30 04:46:38 +00:00
|
|
|
Some(&adds[1])
|
2023-05-15 05:24:06 +00:00
|
|
|
} else if adds[1] == removes[0] {
|
2023-05-30 04:46:38 +00:00
|
|
|
Some(&adds[0])
|
2023-05-15 05:24:06 +00:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-05-13 18:16:55 +00:00
|
|
|
// Number of occurrences of each value, with positive indexes counted as +1 and
|
|
|
|
// negative as -1, thereby letting positive and negative terms with the same
|
|
|
|
// value (i.e. key in the map) cancel each other.
|
2023-05-30 04:46:38 +00:00
|
|
|
let mut counts: HashMap<&T, i32> = HashMap::new();
|
2023-05-13 18:16:55 +00:00
|
|
|
for value in adds.iter() {
|
2023-05-30 04:46:38 +00:00
|
|
|
counts.entry(value).and_modify(|e| *e += 1).or_insert(1);
|
2023-05-13 18:16:55 +00:00
|
|
|
}
|
|
|
|
for value in removes.iter() {
|
2023-05-30 04:46:38 +00:00
|
|
|
counts.entry(value).and_modify(|e| *e -= 1).or_insert(-1);
|
2023-05-13 18:16:55 +00:00
|
|
|
}
|
|
|
|
|
2023-06-13 11:30:00 +00:00
|
|
|
// Collect non-zero value. Values with a count of 0 means that they have
|
|
|
|
// cancelled out.
|
|
|
|
let counts = counts
|
|
|
|
.into_iter()
|
|
|
|
.filter(|&(_, count)| count != 0)
|
2023-05-13 18:16:55 +00:00
|
|
|
.collect_vec();
|
2023-06-13 11:30:00 +00:00
|
|
|
match counts[..] {
|
|
|
|
[(value, 1)] => {
|
|
|
|
// If there is a single value with a count of 1 left, then that is the result.
|
|
|
|
Some(value)
|
|
|
|
}
|
|
|
|
[(value1, count1), (value2, count2)] => {
|
|
|
|
// All sides made the same change.
|
|
|
|
// This matches what Git and Mercurial do (in the 3-way case at least), but not
|
|
|
|
// what Darcs and Pijul do. It means that repeated 3-way merging of multiple
|
|
|
|
// trees may give different results depending on the order of merging.
|
|
|
|
// TODO: Consider removing this special case, making the algorithm more strict,
|
|
|
|
// and maybe add a more lenient version that is used when the user explicitly
|
|
|
|
// asks for conflict resolution.
|
|
|
|
assert_eq!(count1 + count2, 1);
|
|
|
|
if count1 > 0 {
|
|
|
|
Some(value1)
|
|
|
|
} else {
|
|
|
|
Some(value2)
|
|
|
|
}
|
|
|
|
}
|
2023-05-13 18:16:55 +00:00
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-06 16:21:35 +00:00
|
|
|
/// A generic representation of merged values.
|
|
|
|
///
|
|
|
|
/// There is exactly one more `adds()` than `removes()`. When interpreted as a
|
|
|
|
/// series of diffs, the merge's (i+1)-st add is matched with the i-th
|
|
|
|
/// remove. The zeroth add is considered a diff from the non-existent state.
|
|
|
|
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
|
|
|
|
pub struct Merge<T> {
|
|
|
|
removes: Vec<T>,
|
|
|
|
adds: Vec<T>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T> Merge<T> {
|
2023-08-24 04:23:37 +00:00
|
|
|
/// Creates a new merge object from the given removes and adds.
|
2023-08-06 16:21:35 +00:00
|
|
|
pub fn new(removes: Vec<T>, adds: Vec<T>) -> Self {
|
|
|
|
assert_eq!(adds.len(), removes.len() + 1);
|
|
|
|
Merge { removes, adds }
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Creates a `Merge` with a single resolved value.
|
|
|
|
pub fn resolved(value: T) -> Self {
|
|
|
|
Merge::new(vec![], vec![value])
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Create a `Merge` from a `removes` and `adds`, padding with `None` to
|
|
|
|
/// make sure that there is exactly one more `adds` than `removes`.
|
|
|
|
pub fn from_legacy_form(
|
|
|
|
removes: impl IntoIterator<Item = T>,
|
|
|
|
adds: impl IntoIterator<Item = T>,
|
|
|
|
) -> Merge<Option<T>> {
|
|
|
|
let mut removes = removes.into_iter().map(Some).collect_vec();
|
|
|
|
let mut adds = adds.into_iter().map(Some).collect_vec();
|
|
|
|
while removes.len() + 1 < adds.len() {
|
|
|
|
removes.push(None);
|
|
|
|
}
|
|
|
|
while adds.len() < removes.len() + 1 {
|
|
|
|
adds.push(None);
|
|
|
|
}
|
|
|
|
Merge::new(removes, adds)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the removes and adds as a pair.
|
|
|
|
pub fn take(self) -> (Vec<T>, Vec<T>) {
|
|
|
|
(self.removes, self.adds)
|
|
|
|
}
|
|
|
|
|
2023-08-24 04:23:37 +00:00
|
|
|
/// The removed values, also called negative terms.
|
2023-08-06 16:21:35 +00:00
|
|
|
pub fn removes(&self) -> &[T] {
|
|
|
|
&self.removes
|
|
|
|
}
|
|
|
|
|
2023-08-24 04:23:37 +00:00
|
|
|
/// The removed values, also called positive terms.
|
2023-08-06 16:21:35 +00:00
|
|
|
pub fn adds(&self) -> &[T] {
|
|
|
|
&self.adds
|
|
|
|
}
|
|
|
|
|
2023-08-24 01:41:48 +00:00
|
|
|
/// The number of positive terms in the conflict.
|
|
|
|
pub fn num_sides(&self) -> usize {
|
|
|
|
self.adds.len()
|
|
|
|
}
|
|
|
|
|
2023-08-06 16:21:35 +00:00
|
|
|
/// Whether this merge is resolved. Does not resolve trivial merges.
|
|
|
|
pub fn is_resolved(&self) -> bool {
|
|
|
|
self.removes.is_empty()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the resolved value, if this merge is resolved. Does not
|
|
|
|
/// resolve trivial merges.
|
|
|
|
pub fn as_resolved(&self) -> Option<&T> {
|
|
|
|
if let [value] = &self.adds[..] {
|
|
|
|
Some(value)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-06 19:38:08 +00:00
|
|
|
/// Returns the resolved value, if this merge is resolved. Otherwise returns
|
|
|
|
/// the merge itself as an `Err`. Does not resolve trivial merges.
|
|
|
|
pub fn into_resolved(mut self) -> Result<T, Merge<T>> {
|
|
|
|
if self.removes.is_empty() {
|
|
|
|
Ok(self.adds.pop().unwrap())
|
|
|
|
} else {
|
|
|
|
Err(self)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-06 16:21:35 +00:00
|
|
|
/// Simplify the merge by joining diffs like A->B and B->C into A->C.
|
|
|
|
/// Also drops trivial diffs like A->A.
|
|
|
|
pub fn simplify(mut self) -> Self
|
|
|
|
where
|
|
|
|
T: PartialEq,
|
|
|
|
{
|
|
|
|
let mut add_index = 0;
|
|
|
|
while add_index < self.adds.len() {
|
|
|
|
let add = &self.adds[add_index];
|
|
|
|
if let Some(remove_index) = self.removes.iter().position(|remove| remove == add) {
|
|
|
|
// Move the value to the `add_index-1`th diff, then delete the `remove_index`th
|
|
|
|
// diff.
|
|
|
|
self.adds.swap(remove_index + 1, add_index);
|
|
|
|
self.removes.remove(remove_index);
|
|
|
|
self.adds.remove(remove_index + 1);
|
|
|
|
} else {
|
|
|
|
add_index += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2023-08-24 04:23:37 +00:00
|
|
|
/// If this merge can be trivially resolved, returns the value it resolves
|
|
|
|
/// to.
|
2023-08-06 16:21:35 +00:00
|
|
|
pub fn resolve_trivial(&self) -> Option<&T>
|
|
|
|
where
|
|
|
|
T: Eq + Hash,
|
|
|
|
{
|
|
|
|
trivial_merge(&self.removes, &self.adds)
|
|
|
|
}
|
|
|
|
|
2023-08-12 16:59:25 +00:00
|
|
|
/// Returns an iterator over the terms. The items will alternate between
|
|
|
|
/// positive and negative terms, starting with positive (since there's one
|
|
|
|
/// more of those).
|
|
|
|
pub fn iter(&self) -> Iter<'_, T> {
|
|
|
|
Iter::new(self)
|
|
|
|
}
|
|
|
|
|
2023-08-06 16:21:35 +00:00
|
|
|
/// Creates a new merge by applying `f` to each remove and add.
|
2023-08-12 16:59:25 +00:00
|
|
|
pub fn map<'a, U>(&'a self, f: impl FnMut(&'a T) -> U) -> Merge<U> {
|
|
|
|
let builder: MergeBuilder<U> = self.iter().map(f).collect();
|
|
|
|
builder.build()
|
2023-08-06 16:21:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Creates a new merge by applying `f` to each remove and add, returning
|
|
|
|
/// `None if `f` returns `None` for any of them.
|
2023-08-12 16:59:25 +00:00
|
|
|
pub fn maybe_map<'a, U>(&'a self, f: impl FnMut(&'a T) -> Option<U>) -> Option<Merge<U>> {
|
|
|
|
let builder: Option<MergeBuilder<U>> = self.iter().map(f).collect();
|
|
|
|
builder.map(MergeBuilder::build)
|
2023-08-06 16:21:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Creates a new merge by applying `f` to each remove and add, returning
|
|
|
|
/// `Err if `f` returns `Err` for any of them.
|
|
|
|
pub fn try_map<'a, U, E>(
|
|
|
|
&'a self,
|
2023-08-12 16:59:25 +00:00
|
|
|
f: impl FnMut(&'a T) -> Result<U, E>,
|
2023-08-06 16:21:35 +00:00
|
|
|
) -> Result<Merge<U>, E> {
|
2023-08-12 16:59:25 +00:00
|
|
|
let builder: MergeBuilder<U> = self.iter().map(f).try_collect()?;
|
|
|
|
Ok(builder.build())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-24 04:23:37 +00:00
|
|
|
/// Iterator over the terms in a merge. See `Merge::iter()`.
|
2023-08-12 16:59:25 +00:00
|
|
|
pub struct Iter<'a, T> {
|
|
|
|
merge: &'a Merge<T>,
|
|
|
|
i: usize,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, T> Iter<'a, T> {
|
|
|
|
fn new(merge: &'a Merge<T>) -> Self {
|
|
|
|
Self { merge, i: 0 }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, T> Iterator for Iter<'a, T> {
|
|
|
|
type Item = &'a T;
|
|
|
|
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
|
|
if self.i > 2 * self.merge.removes.len() {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
let item = if self.i % 2 == 0 {
|
|
|
|
&self.merge.adds[self.i / 2]
|
|
|
|
} else {
|
|
|
|
&self.merge.removes[self.i / 2]
|
|
|
|
};
|
|
|
|
self.i += 1;
|
|
|
|
Some(item)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Helper for consuming items from an iterator and then creating a `Merge`.
|
|
|
|
///
|
|
|
|
/// By not collecting directly into `Merge`, we can avoid creating invalid
|
|
|
|
/// instances of it. If we had `Merge::from_iter()` we would need to allow it to
|
|
|
|
/// accept iterators of any length (including 0). We couldn't make it panic on
|
|
|
|
/// even lengths because we can get passed such iterators from e.g.
|
|
|
|
/// `Option::from_iter()`. By collecting into `MergeBuilder` instead, we move
|
|
|
|
/// the checking until after `from_iter()` (to `MergeBuilder::build()`).
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
|
|
pub struct MergeBuilder<T> {
|
|
|
|
removes: Vec<T>,
|
|
|
|
adds: Vec<T>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T> MergeBuilder<T> {
|
|
|
|
/// Requires that exactly one more "adds" than "removes" have been added to
|
|
|
|
/// this builder.
|
|
|
|
pub fn build(self) -> Merge<T> {
|
|
|
|
Merge::new(self.removes, self.adds)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T> FromIterator<T> for MergeBuilder<T> {
|
|
|
|
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
|
|
|
|
let mut removes = vec![];
|
|
|
|
let mut adds = vec![];
|
|
|
|
let mut curr = &mut adds;
|
|
|
|
let mut next = &mut removes;
|
|
|
|
for item in iter {
|
|
|
|
curr.push(item);
|
|
|
|
std::mem::swap(&mut curr, &mut next);
|
|
|
|
}
|
|
|
|
MergeBuilder { removes, adds }
|
2023-08-06 16:21:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T> Merge<Option<T>> {
|
2023-08-06 17:35:20 +00:00
|
|
|
/// Creates a resolved merge with a value of `None`.
|
|
|
|
pub fn absent() -> Self {
|
|
|
|
Self::resolved(None)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Creates a resolved merge with a value of `Some(value)`.
|
|
|
|
pub fn normal(value: T) -> Self {
|
|
|
|
Self::resolved(Some(value))
|
|
|
|
}
|
|
|
|
|
2023-08-24 04:23:37 +00:00
|
|
|
/// Whether this represents a resolved value of `None`.
|
2023-08-06 19:26:08 +00:00
|
|
|
pub fn is_absent(&self) -> bool {
|
|
|
|
matches!(self.as_resolved(), Some(None))
|
|
|
|
}
|
|
|
|
|
2023-08-24 04:23:37 +00:00
|
|
|
/// The opposite of `is_absent()`.
|
2023-08-06 19:26:08 +00:00
|
|
|
pub fn is_present(&self) -> bool {
|
|
|
|
!self.is_absent()
|
|
|
|
}
|
|
|
|
|
2023-08-06 16:21:35 +00:00
|
|
|
/// Creates lists of `removes` and `adds` from a `Merge` by dropping
|
|
|
|
/// `None` values. Note that the conversion is lossy: the order of `None`
|
|
|
|
/// values is not preserved when converting back to a `Merge`.
|
|
|
|
pub fn into_legacy_form(self) -> (Vec<T>, Vec<T>) {
|
|
|
|
(
|
|
|
|
self.removes.into_iter().flatten().collect(),
|
|
|
|
self.adds.into_iter().flatten().collect(),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T> Merge<Merge<T>> {
|
|
|
|
/// Flattens a nested merge into a regular merge.
|
|
|
|
///
|
|
|
|
/// Let's say we have a 3-way merge of 3-way merges like this:
|
|
|
|
///
|
|
|
|
/// 4 5 7 8
|
|
|
|
/// 3 6
|
|
|
|
/// 1 2
|
|
|
|
/// 0
|
|
|
|
///
|
|
|
|
/// Flattening that results in this 9-way merge:
|
|
|
|
///
|
|
|
|
/// 4 5 0 7 8
|
|
|
|
/// 3 2 1 6
|
|
|
|
pub fn flatten(mut self) -> Merge<T> {
|
|
|
|
self.removes.reverse();
|
|
|
|
self.adds.reverse();
|
|
|
|
let mut result = self.adds.pop().unwrap();
|
|
|
|
while let Some(mut remove) = self.removes.pop() {
|
|
|
|
// Add removes reversed, and with the first element moved last, so we preserve
|
|
|
|
// the diffs
|
|
|
|
let first_add = remove.adds.remove(0);
|
|
|
|
result.removes.extend(remove.adds);
|
|
|
|
result.removes.push(first_add);
|
|
|
|
result.adds.extend(remove.removes);
|
|
|
|
let add = self.adds.pop().unwrap();
|
|
|
|
result.removes.extend(add.removes);
|
|
|
|
result.adds.extend(add.adds);
|
|
|
|
}
|
|
|
|
assert!(self.adds.is_empty());
|
|
|
|
result
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T: ContentHash> ContentHash for Merge<T> {
|
|
|
|
fn hash(&self, state: &mut impl digest::Update) {
|
|
|
|
self.removes().hash(state);
|
|
|
|
self.adds().hash(state);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Merge<TreeId> {
|
2023-08-24 04:23:37 +00:00
|
|
|
/// Creates a resolved merge for a legacy tree id (same as
|
|
|
|
/// `Merge::resolved()`).
|
2023-08-06 16:21:35 +00:00
|
|
|
// TODO(#1624): delete when all callers have been updated to support tree-level
|
|
|
|
// conflicts
|
|
|
|
pub fn from_legacy_tree_id(value: TreeId) -> Self {
|
2023-08-24 04:23:37 +00:00
|
|
|
Merge::resolved(value)
|
2023-08-06 16:21:35 +00:00
|
|
|
}
|
|
|
|
|
2023-08-24 04:23:37 +00:00
|
|
|
/// Assumes that this merge is resolved and returns a reference to the
|
|
|
|
/// value. Panics otherwise.
|
2023-08-06 16:21:35 +00:00
|
|
|
// TODO(#1624): delete when all callers have been updated to support tree-level
|
|
|
|
// conflicts
|
|
|
|
pub fn as_legacy_tree_id(&self) -> &TreeId {
|
|
|
|
self.as_resolved().unwrap()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Merge<Option<TreeValue>> {
|
|
|
|
/// Create a `Merge` from a `backend::Conflict`, padding with `None` to
|
|
|
|
/// make sure that there is exactly one more `adds()` than `removes()`.
|
|
|
|
pub fn from_backend_conflict(conflict: backend::Conflict) -> Self {
|
|
|
|
let removes = conflict.removes.into_iter().map(|term| term.value);
|
|
|
|
let adds = conflict.adds.into_iter().map(|term| term.value);
|
|
|
|
Merge::from_legacy_form(removes, adds)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Creates a `backend::Conflict` from a `Merge` by dropping `None`
|
|
|
|
/// values. Note that the conversion is lossy: the order of `None` values is
|
|
|
|
/// not preserved when converting back to a `Merge`.
|
|
|
|
pub fn into_backend_conflict(self) -> backend::Conflict {
|
|
|
|
let (removes, adds) = self.into_legacy_form();
|
|
|
|
let removes = removes
|
|
|
|
.into_iter()
|
|
|
|
.map(|value| backend::ConflictTerm { value })
|
|
|
|
.collect();
|
|
|
|
let adds = adds
|
|
|
|
.into_iter()
|
|
|
|
.map(|value| backend::ConflictTerm { value })
|
|
|
|
.collect();
|
|
|
|
backend::Conflict { removes, adds }
|
|
|
|
}
|
|
|
|
|
2023-07-06 06:03:55 +00:00
|
|
|
/// Whether this merge should be recursed into when doing directory walks.
|
|
|
|
pub fn is_tree(&self) -> bool {
|
|
|
|
self.is_present()
|
|
|
|
&& self
|
|
|
|
.iter()
|
|
|
|
.all(|value| matches!(value, Some(TreeValue::Tree(_)) | None))
|
|
|
|
}
|
|
|
|
|
2023-08-24 04:23:37 +00:00
|
|
|
/// If this merge contains only non-executable files or absent entries,
|
|
|
|
/// returns a merge of the `FileId`s`.
|
2023-08-06 16:21:35 +00:00
|
|
|
pub fn to_file_merge(&self) -> Option<Merge<Option<FileId>>> {
|
|
|
|
self.maybe_map(|term| match term {
|
|
|
|
None => Some(None),
|
|
|
|
Some(TreeValue::File {
|
|
|
|
id,
|
|
|
|
executable: false,
|
|
|
|
}) => Some(Some(id.clone())),
|
|
|
|
_ => None,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-08-11 05:06:38 +00:00
|
|
|
/// Creates a new merge with the file ids from the given merge. In other
|
|
|
|
/// words, only the executable bits from `self` will be preserved.
|
|
|
|
pub fn with_new_file_ids(&self, file_ids: &Merge<Option<FileId>>) -> Self {
|
|
|
|
assert_eq!(self.removes.len(), file_ids.removes.len());
|
2023-08-12 16:59:25 +00:00
|
|
|
let builder: MergeBuilder<Option<TreeValue>> = zip(self.iter(), file_ids.iter())
|
|
|
|
.map(|(tree_value, file_id)| {
|
2023-08-11 05:06:38 +00:00
|
|
|
if let Some(TreeValue::File { id: _, executable }) = tree_value {
|
2023-08-12 16:59:25 +00:00
|
|
|
Some(TreeValue::File {
|
2023-08-11 05:06:38 +00:00
|
|
|
id: file_id.as_ref().unwrap().clone(),
|
|
|
|
executable: *executable,
|
2023-08-12 16:59:25 +00:00
|
|
|
})
|
2023-08-11 05:06:38 +00:00
|
|
|
} else {
|
|
|
|
assert!(tree_value.is_none());
|
|
|
|
assert!(file_id.is_none());
|
2023-08-12 16:59:25 +00:00
|
|
|
None
|
2023-08-11 05:06:38 +00:00
|
|
|
}
|
2023-08-12 16:59:25 +00:00
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
builder.build()
|
2023-08-11 05:06:38 +00:00
|
|
|
}
|
|
|
|
|
2023-08-06 16:21:35 +00:00
|
|
|
/// Give a summary description of the conflict's "removes" and "adds"
|
|
|
|
pub fn describe(&self, file: &mut dyn Write) -> std::io::Result<()> {
|
|
|
|
file.write_all(b"Conflict:\n")?;
|
|
|
|
for term in self.removes().iter().flatten() {
|
|
|
|
file.write_all(format!(" Removing {}\n", describe_conflict_term(term)).as_bytes())?;
|
|
|
|
}
|
|
|
|
for term in self.adds().iter().flatten() {
|
|
|
|
file.write_all(format!(" Adding {}\n", describe_conflict_term(term)).as_bytes())?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T> Merge<Option<T>>
|
|
|
|
where
|
|
|
|
T: Borrow<TreeValue>,
|
|
|
|
{
|
|
|
|
/// If every non-`None` term of a `Merge<Option<TreeValue>>`
|
|
|
|
/// is a `TreeValue::Tree`, this converts it to
|
|
|
|
/// a `Merge<Tree>`, with empty trees instead of
|
|
|
|
/// any `None` terms. Otherwise, returns `None`.
|
|
|
|
pub fn to_tree_merge(
|
|
|
|
&self,
|
|
|
|
store: &Arc<Store>,
|
|
|
|
dir: &RepoPath,
|
|
|
|
) -> Result<Option<Merge<Tree>>, BackendError> {
|
|
|
|
let tree_id_merge = self.maybe_map(|term| match term {
|
|
|
|
None => Some(None),
|
|
|
|
Some(value) => {
|
|
|
|
if let TreeValue::Tree(id) = value.borrow() {
|
|
|
|
Some(Some(id))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if let Some(tree_id_merge) = tree_id_merge {
|
|
|
|
let get_tree = |id: &Option<&TreeId>| -> Result<Tree, BackendError> {
|
|
|
|
if let Some(id) = id {
|
|
|
|
store.get_tree(dir, id)
|
|
|
|
} else {
|
|
|
|
Ok(Tree::null(store.clone(), dir.clone()))
|
|
|
|
}
|
|
|
|
};
|
|
|
|
Ok(Some(tree_id_merge.try_map(get_tree)?))
|
|
|
|
} else {
|
|
|
|
Ok(None)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn describe_conflict_term(value: &TreeValue) -> String {
|
|
|
|
match value {
|
|
|
|
TreeValue::File {
|
|
|
|
id,
|
|
|
|
executable: false,
|
|
|
|
} => {
|
|
|
|
format!("file with id {}", id.hex())
|
|
|
|
}
|
|
|
|
TreeValue::File {
|
|
|
|
id,
|
|
|
|
executable: true,
|
|
|
|
} => {
|
|
|
|
format!("executable file with id {}", id.hex())
|
|
|
|
}
|
|
|
|
TreeValue::Symlink(id) => {
|
|
|
|
format!("symlink with id {}", id.hex())
|
|
|
|
}
|
|
|
|
TreeValue::Tree(id) => {
|
|
|
|
format!("tree with id {}", id.hex())
|
|
|
|
}
|
|
|
|
TreeValue::GitSubmodule(id) => {
|
|
|
|
format!("Git submodule with id {}", id.hex())
|
|
|
|
}
|
|
|
|
TreeValue::Conflict(id) => {
|
|
|
|
format!("Conflict with id {}", id.hex())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-06 22:10:24 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
2023-08-06 22:12:15 +00:00
|
|
|
fn c<T: Clone>(removes: &[T], adds: &[T]) -> Merge<T> {
|
|
|
|
Merge::new(removes.to_vec(), adds.to_vec())
|
|
|
|
}
|
|
|
|
|
2023-08-06 22:10:24 +00:00
|
|
|
#[test]
|
|
|
|
fn test_trivial_merge() {
|
|
|
|
assert_eq!(trivial_merge(&[], &[0]), Some(&0));
|
|
|
|
assert_eq!(trivial_merge(&[0], &[0, 0]), Some(&0));
|
|
|
|
assert_eq!(trivial_merge(&[0], &[0, 1]), Some(&1));
|
|
|
|
assert_eq!(trivial_merge(&[0], &[1, 0]), Some(&1));
|
|
|
|
assert_eq!(trivial_merge(&[0], &[1, 1]), Some(&1));
|
|
|
|
assert_eq!(trivial_merge(&[0], &[1, 2]), None);
|
|
|
|
assert_eq!(trivial_merge(&[0, 0], &[0, 0, 0]), Some(&0));
|
|
|
|
assert_eq!(trivial_merge(&[0, 0], &[0, 0, 1]), Some(&1));
|
|
|
|
assert_eq!(trivial_merge(&[0, 0], &[0, 1, 0]), Some(&1));
|
|
|
|
assert_eq!(trivial_merge(&[0, 0], &[0, 1, 1]), Some(&1));
|
|
|
|
assert_eq!(trivial_merge(&[0, 0], &[0, 1, 2]), None);
|
|
|
|
assert_eq!(trivial_merge(&[0, 0], &[1, 0, 0]), Some(&1));
|
|
|
|
assert_eq!(trivial_merge(&[0, 0], &[1, 0, 1]), Some(&1));
|
|
|
|
assert_eq!(trivial_merge(&[0, 0], &[1, 0, 2]), None);
|
|
|
|
assert_eq!(trivial_merge(&[0, 0], &[1, 1, 0]), Some(&1));
|
|
|
|
assert_eq!(trivial_merge(&[0, 0], &[1, 1, 1]), Some(&1));
|
|
|
|
assert_eq!(trivial_merge(&[0, 0], &[1, 1, 2]), None);
|
|
|
|
assert_eq!(trivial_merge(&[0, 0], &[1, 2, 0]), None);
|
|
|
|
assert_eq!(trivial_merge(&[0, 0], &[1, 2, 1]), None);
|
|
|
|
assert_eq!(trivial_merge(&[0, 0], &[1, 2, 2]), None);
|
|
|
|
assert_eq!(trivial_merge(&[0, 0], &[1, 2, 3]), None);
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[0, 0, 0]), Some(&0));
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[0, 0, 1]), Some(&0));
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[0, 0, 2]), None);
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[0, 1, 0]), Some(&0));
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[0, 1, 1]), Some(&1));
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[0, 1, 2]), Some(&2));
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[0, 2, 0]), None);
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[0, 2, 1]), Some(&2));
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[0, 2, 2]), Some(&2));
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[0, 2, 3]), None);
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[1, 0, 0]), Some(&0));
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[1, 0, 1]), Some(&1));
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[1, 0, 2]), Some(&2));
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[1, 1, 0]), Some(&1));
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[1, 1, 1]), Some(&1));
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[1, 1, 2]), None);
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[1, 2, 0]), Some(&2));
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[1, 2, 1]), None);
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[1, 2, 2]), Some(&2));
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[1, 2, 3]), None);
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[2, 0, 0]), None);
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[2, 0, 1]), Some(&2));
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[2, 0, 2]), Some(&2));
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[2, 0, 3]), None);
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[2, 1, 0]), Some(&2));
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[2, 1, 1]), None);
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[2, 1, 2]), Some(&2));
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[2, 1, 3]), None);
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[2, 2, 0]), Some(&2));
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[2, 2, 1]), Some(&2));
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[2, 2, 2]), None);
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[2, 2, 3]), None);
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[2, 3, 0]), None);
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[2, 3, 1]), None);
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[2, 3, 2]), None);
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[2, 3, 3]), None);
|
|
|
|
assert_eq!(trivial_merge(&[0, 1], &[2, 3, 4]), None);
|
|
|
|
}
|
2023-08-06 22:12:15 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_legacy_form_conversion() {
|
|
|
|
fn test_equivalent<T>(legacy_form: (Vec<T>, Vec<T>), merge: Merge<Option<T>>)
|
|
|
|
where
|
|
|
|
T: Clone + PartialEq + std::fmt::Debug,
|
|
|
|
{
|
|
|
|
assert_eq!(merge.clone().into_legacy_form(), legacy_form);
|
|
|
|
assert_eq!(Merge::from_legacy_form(legacy_form.0, legacy_form.1), merge);
|
|
|
|
}
|
|
|
|
// Non-conflict
|
|
|
|
test_equivalent((vec![], vec![0]), Merge::new(vec![], vec![Some(0)]));
|
|
|
|
// Regular 3-way conflict
|
|
|
|
test_equivalent(
|
|
|
|
(vec![0], vec![1, 2]),
|
|
|
|
Merge::new(vec![Some(0)], vec![Some(1), Some(2)]),
|
|
|
|
);
|
|
|
|
// Modify/delete conflict
|
|
|
|
test_equivalent(
|
|
|
|
(vec![0], vec![1]),
|
|
|
|
Merge::new(vec![Some(0)], vec![Some(1), None]),
|
|
|
|
);
|
|
|
|
// Add/add conflict
|
|
|
|
test_equivalent(
|
|
|
|
(vec![], vec![0, 1]),
|
|
|
|
Merge::new(vec![None], vec![Some(0), Some(1)]),
|
|
|
|
);
|
|
|
|
// 5-way conflict
|
|
|
|
test_equivalent(
|
|
|
|
(vec![0, 1], vec![2, 3, 4]),
|
|
|
|
Merge::new(vec![Some(0), Some(1)], vec![Some(2), Some(3), Some(4)]),
|
|
|
|
);
|
|
|
|
// 5-way delete/delete conflict
|
|
|
|
test_equivalent(
|
|
|
|
(vec![0, 1], vec![]),
|
|
|
|
Merge::new(vec![Some(0), Some(1)], vec![None, None, None]),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_as_resolved() {
|
|
|
|
assert_eq!(Merge::new(vec![], vec![0]).as_resolved(), Some(&0));
|
|
|
|
// Even a trivially resolvable merge is not resolved
|
|
|
|
assert_eq!(Merge::new(vec![0], vec![0, 1]).as_resolved(), None);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_simplify() {
|
|
|
|
// 1-way merge
|
|
|
|
assert_eq!(c(&[], &[0]).simplify(), c(&[], &[0]));
|
|
|
|
// 3-way merge
|
|
|
|
assert_eq!(c(&[0], &[0, 0]).simplify(), c(&[], &[0]));
|
|
|
|
assert_eq!(c(&[0], &[0, 1]).simplify(), c(&[], &[1]));
|
|
|
|
assert_eq!(c(&[0], &[1, 0]).simplify(), c(&[], &[1]));
|
|
|
|
assert_eq!(c(&[0], &[1, 1]).simplify(), c(&[0], &[1, 1]));
|
|
|
|
assert_eq!(c(&[0], &[1, 2]).simplify(), c(&[0], &[1, 2]));
|
|
|
|
// 5-way merge
|
|
|
|
assert_eq!(c(&[0, 0], &[0, 0, 0]).simplify(), c(&[], &[0]));
|
|
|
|
assert_eq!(c(&[0, 0], &[0, 0, 1]).simplify(), c(&[], &[1]));
|
|
|
|
assert_eq!(c(&[0, 0], &[0, 1, 0]).simplify(), c(&[], &[1]));
|
|
|
|
assert_eq!(c(&[0, 0], &[0, 1, 1]).simplify(), c(&[0], &[1, 1]));
|
|
|
|
assert_eq!(c(&[0, 0], &[0, 1, 2]).simplify(), c(&[0], &[1, 2]));
|
|
|
|
assert_eq!(c(&[0, 0], &[1, 0, 0]).simplify(), c(&[], &[1]));
|
|
|
|
assert_eq!(c(&[0, 0], &[1, 0, 1]).simplify(), c(&[0], &[1, 1]));
|
|
|
|
assert_eq!(c(&[0, 0], &[1, 0, 2]).simplify(), c(&[0], &[1, 2]));
|
|
|
|
assert_eq!(c(&[0, 0], &[1, 1, 0]).simplify(), c(&[0], &[1, 1]));
|
|
|
|
assert_eq!(c(&[0, 0], &[1, 1, 1]).simplify(), c(&[0, 0], &[1, 1, 1]));
|
|
|
|
assert_eq!(c(&[0, 0], &[1, 1, 2]).simplify(), c(&[0, 0], &[1, 1, 2]));
|
|
|
|
assert_eq!(c(&[0, 0], &[1, 2, 0]).simplify(), c(&[0], &[1, 2]));
|
|
|
|
assert_eq!(c(&[0, 0], &[1, 2, 1]).simplify(), c(&[0, 0], &[1, 2, 1]));
|
|
|
|
assert_eq!(c(&[0, 0], &[1, 2, 2]).simplify(), c(&[0, 0], &[1, 2, 2]));
|
|
|
|
assert_eq!(c(&[0, 0], &[1, 2, 3]).simplify(), c(&[0, 0], &[1, 2, 3]));
|
|
|
|
assert_eq!(c(&[0, 1], &[0, 0, 0]).simplify(), c(&[1], &[0, 0]));
|
|
|
|
assert_eq!(c(&[0, 1], &[0, 0, 1]).simplify(), c(&[], &[0]));
|
|
|
|
assert_eq!(c(&[0, 1], &[0, 0, 2]).simplify(), c(&[1], &[0, 2]));
|
|
|
|
assert_eq!(c(&[0, 1], &[0, 1, 0]).simplify(), c(&[], &[0]));
|
|
|
|
assert_eq!(c(&[0, 1], &[0, 1, 1]).simplify(), c(&[], &[1]));
|
|
|
|
assert_eq!(c(&[0, 1], &[0, 1, 2]).simplify(), c(&[], &[2]));
|
|
|
|
assert_eq!(c(&[0, 1], &[0, 2, 0]).simplify(), c(&[1], &[2, 0]));
|
|
|
|
assert_eq!(c(&[0, 1], &[0, 2, 1]).simplify(), c(&[], &[2]));
|
|
|
|
assert_eq!(c(&[0, 1], &[0, 2, 2]).simplify(), c(&[1], &[2, 2]));
|
|
|
|
assert_eq!(c(&[0, 1], &[0, 2, 3]).simplify(), c(&[1], &[2, 3]));
|
|
|
|
assert_eq!(c(&[0, 1], &[1, 0, 0]).simplify(), c(&[], &[0]));
|
|
|
|
assert_eq!(c(&[0, 1], &[1, 0, 1]).simplify(), c(&[], &[1]));
|
|
|
|
assert_eq!(c(&[0, 1], &[1, 0, 2]).simplify(), c(&[], &[2]));
|
|
|
|
assert_eq!(c(&[0, 1], &[1, 1, 0]).simplify(), c(&[], &[1]));
|
|
|
|
assert_eq!(c(&[0, 1], &[1, 1, 1]).simplify(), c(&[0], &[1, 1]));
|
|
|
|
assert_eq!(c(&[0, 1], &[1, 1, 2]).simplify(), c(&[0], &[2, 1]));
|
|
|
|
assert_eq!(c(&[0, 1], &[1, 2, 0]).simplify(), c(&[], &[2]));
|
|
|
|
assert_eq!(c(&[0, 1], &[1, 2, 1]).simplify(), c(&[0], &[1, 2]));
|
|
|
|
assert_eq!(c(&[0, 1], &[1, 2, 2]).simplify(), c(&[0], &[2, 2]));
|
|
|
|
assert_eq!(c(&[0, 1], &[1, 2, 3]).simplify(), c(&[0], &[3, 2]));
|
|
|
|
assert_eq!(c(&[0, 1], &[2, 0, 0]).simplify(), c(&[1], &[2, 0]));
|
|
|
|
assert_eq!(c(&[0, 1], &[2, 0, 1]).simplify(), c(&[], &[2]));
|
|
|
|
assert_eq!(c(&[0, 1], &[2, 0, 2]).simplify(), c(&[1], &[2, 2]));
|
|
|
|
assert_eq!(c(&[0, 1], &[2, 0, 3]).simplify(), c(&[1], &[2, 3]));
|
|
|
|
assert_eq!(c(&[0, 1], &[2, 1, 0]).simplify(), c(&[], &[2]));
|
|
|
|
assert_eq!(c(&[0, 1], &[2, 1, 1]).simplify(), c(&[0], &[2, 1]));
|
|
|
|
assert_eq!(c(&[0, 1], &[2, 1, 2]).simplify(), c(&[0], &[2, 2]));
|
|
|
|
assert_eq!(c(&[0, 1], &[2, 1, 3]).simplify(), c(&[0], &[2, 3]));
|
|
|
|
assert_eq!(c(&[0, 1], &[2, 2, 0]).simplify(), c(&[1], &[2, 2]));
|
|
|
|
assert_eq!(c(&[0, 1], &[2, 2, 1]).simplify(), c(&[0], &[2, 2]));
|
|
|
|
assert_eq!(c(&[0, 1], &[2, 2, 2]).simplify(), c(&[0, 1], &[2, 2, 2]));
|
|
|
|
assert_eq!(c(&[0, 1], &[2, 2, 3]).simplify(), c(&[0, 1], &[2, 2, 3]));
|
|
|
|
assert_eq!(c(&[0, 1], &[2, 3, 0]).simplify(), c(&[1], &[2, 3]));
|
|
|
|
assert_eq!(c(&[0, 1], &[2, 3, 1]).simplify(), c(&[0], &[2, 3]));
|
|
|
|
assert_eq!(c(&[0, 1], &[2, 3, 2]).simplify(), c(&[0, 1], &[2, 3, 2]));
|
|
|
|
assert_eq!(c(&[0, 1], &[2, 3, 3]).simplify(), c(&[0, 1], &[2, 3, 3]));
|
|
|
|
assert_eq!(c(&[0, 1], &[2, 3, 4]).simplify(), c(&[0, 1], &[2, 3, 4]));
|
|
|
|
assert_eq!(
|
|
|
|
c(&[0, 1, 2], &[3, 4, 5, 0]).simplify(),
|
|
|
|
c(&[1, 2], &[3, 5, 4])
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_merge_invariants() {
|
|
|
|
fn check_invariants(removes: &[u32], adds: &[u32]) {
|
|
|
|
let merge = Merge::new(removes.to_vec(), adds.to_vec());
|
|
|
|
// `simplify()` is idempotent
|
|
|
|
assert_eq!(
|
|
|
|
merge.clone().simplify().simplify(),
|
|
|
|
merge.clone().simplify(),
|
|
|
|
"simplify() not idempotent for {merge:?}"
|
|
|
|
);
|
|
|
|
// `resolve_trivial()` is unaffected by `simplify()`
|
|
|
|
assert_eq!(
|
|
|
|
merge.clone().simplify().resolve_trivial(),
|
|
|
|
merge.resolve_trivial(),
|
|
|
|
"simplify() changed result of resolve_trivial() for {merge:?}"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
// 1-way merge
|
|
|
|
check_invariants(&[], &[0]);
|
|
|
|
for i in 0..=1 {
|
|
|
|
for j in 0..=i + 1 {
|
|
|
|
// 3-way merge
|
|
|
|
check_invariants(&[0], &[i, j]);
|
|
|
|
for k in 0..=j + 1 {
|
|
|
|
for l in 0..=k + 1 {
|
|
|
|
// 5-way merge
|
|
|
|
check_invariants(&[0, i], &[j, k, l]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-12 16:59:25 +00:00
|
|
|
#[test]
|
|
|
|
fn test_iter() {
|
|
|
|
// 1-way merge
|
|
|
|
assert_eq!(c(&[], &[1]).iter().collect_vec(), vec![&1]);
|
|
|
|
// 5-way merge
|
|
|
|
assert_eq!(
|
|
|
|
c(&[1, 2], &[3, 4, 5]).iter().collect_vec(),
|
|
|
|
vec![&3, &1, &4, &2, &5]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_from_iter() {
|
|
|
|
// 1-way merge
|
|
|
|
assert_eq!(MergeBuilder::from_iter([1]).build(), c(&[], &[1]));
|
|
|
|
// 5-way merge
|
|
|
|
assert_eq!(
|
|
|
|
MergeBuilder::from_iter([1, 2, 3, 4, 5]).build(),
|
|
|
|
c(&[2, 4], &[1, 3, 5])
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[should_panic]
|
|
|
|
fn test_from_iter_empty() {
|
|
|
|
MergeBuilder::from_iter([1; 0]).build();
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[should_panic]
|
|
|
|
fn test_from_iter_even() {
|
|
|
|
MergeBuilder::from_iter([1, 2]).build();
|
|
|
|
}
|
|
|
|
|
2023-08-06 22:12:15 +00:00
|
|
|
#[test]
|
|
|
|
fn test_map() {
|
|
|
|
fn increment(i: &i32) -> i32 {
|
|
|
|
i + 1
|
|
|
|
}
|
|
|
|
// 1-way merge
|
|
|
|
assert_eq!(c(&[], &[1]).map(increment), c(&[], &[2]));
|
|
|
|
// 3-way merge
|
|
|
|
assert_eq!(c(&[1], &[3, 5]).map(increment), c(&[2], &[4, 6]));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_maybe_map() {
|
|
|
|
fn sqrt(i: &i32) -> Option<i32> {
|
|
|
|
if *i >= 0 {
|
|
|
|
Some((*i as f64).sqrt() as i32)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// 1-way merge
|
|
|
|
assert_eq!(c(&[], &[1]).maybe_map(sqrt), Some(c(&[], &[1])));
|
|
|
|
assert_eq!(c(&[], &[-1]).maybe_map(sqrt), None);
|
|
|
|
// 3-way merge
|
|
|
|
assert_eq!(c(&[1], &[4, 9]).maybe_map(sqrt), Some(c(&[1], &[2, 3])));
|
|
|
|
assert_eq!(c(&[-1], &[4, 9]).maybe_map(sqrt), None);
|
|
|
|
assert_eq!(c(&[1], &[-4, 9]).maybe_map(sqrt), None);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_try_map() {
|
|
|
|
fn sqrt(i: &i32) -> Result<i32, ()> {
|
|
|
|
if *i >= 0 {
|
|
|
|
Ok((*i as f64).sqrt() as i32)
|
|
|
|
} else {
|
|
|
|
Err(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// 1-way merge
|
|
|
|
assert_eq!(c(&[], &[1]).try_map(sqrt), Ok(c(&[], &[1])));
|
|
|
|
assert_eq!(c(&[], &[-1]).try_map(sqrt), Err(()));
|
|
|
|
// 3-way merge
|
|
|
|
assert_eq!(c(&[1], &[4, 9]).try_map(sqrt), Ok(c(&[1], &[2, 3])));
|
|
|
|
assert_eq!(c(&[-1], &[4, 9]).try_map(sqrt), Err(()));
|
|
|
|
assert_eq!(c(&[1], &[-4, 9]).try_map(sqrt), Err(()));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_flatten() {
|
|
|
|
// 1-way merge of 1-way merge
|
|
|
|
assert_eq!(c(&[], &[c(&[], &[0])]).flatten(), c(&[], &[0]));
|
|
|
|
// 1-way merge of 3-way merge
|
|
|
|
assert_eq!(c(&[], &[c(&[0], &[1, 2])]).flatten(), c(&[0], &[1, 2]));
|
|
|
|
// 3-way merge of 1-way merges
|
|
|
|
assert_eq!(
|
|
|
|
c(&[c(&[], &[0])], &[c(&[], &[1]), c(&[], &[2])]).flatten(),
|
|
|
|
c(&[0], &[1, 2])
|
|
|
|
);
|
|
|
|
// 3-way merge of 3-way merges
|
|
|
|
assert_eq!(
|
|
|
|
c(&[c(&[0], &[1, 2])], &[c(&[3], &[4, 5]), c(&[6], &[7, 8])]).flatten(),
|
|
|
|
c(&[3, 2, 1, 6], &[4, 5, 0, 7, 8])
|
|
|
|
);
|
|
|
|
}
|
2023-08-06 22:10:24 +00:00
|
|
|
}
|