diff --git a/cli/src/merge_tools/builtin.rs b/cli/src/merge_tools/builtin.rs index 200ec2a736..ecf31a00a1 100644 --- a/cli/src/merge_tools/builtin.rs +++ b/cli/src/merge_tools/builtin.rs @@ -722,13 +722,11 @@ mod tests { } } } - let merge = Merge::new( - vec![to_file_id(base_tree.path_value(&path))], - vec![ - to_file_id(left_tree.path_value(&path)), - to_file_id(right_tree.path_value(&path)), - ], - ); + let merge = Merge::from_vec(vec![ + to_file_id(left_tree.path_value(&path)), + to_file_id(base_tree.path_value(&path)), + to_file_id(right_tree.path_value(&path)), + ]); let content = extract_as_single_hunk(&merge, store, &path).block_on(); let slices = content.map(|ContentHunk(buf)| buf.as_slice()); let merge_result = files::merge(&slices); diff --git a/lib/src/conflicts.rs b/lib/src/conflicts.rs index 0470244a55..31ce0291f2 100644 --- a/lib/src/conflicts.rs +++ b/lib/src/conflicts.rs @@ -285,7 +285,7 @@ fn parse_conflict_hunk(input: &[u8]) -> Merge { } } - Merge::new(removes, adds) + Merge::from_removes_adds(removes, adds) } /// Parses conflict markers in `content` and returns an updated version of diff --git a/lib/src/files.rs b/lib/src/files.rs index c88f6d7dc1..62346c65ff 100644 --- a/lib/src/files.rs +++ b/lib/src/files.rs @@ -190,15 +190,13 @@ pub fn merge(slices: &Merge<&[u8]>) -> MergeResult { merge_hunks.push(Merge::resolved(resolved_hunk)); resolved_hunk = ContentHunk(vec![]); } - merge_hunks.push(Merge::new( + merge_hunks.push(Merge::from_removes_adds( parts[..num_diffs] .iter() - .map(|part| ContentHunk(part.to_vec())) - .collect_vec(), + .map(|part| ContentHunk(part.to_vec())), parts[num_diffs..] .iter() - .map(|part| ContentHunk(part.to_vec())) - .collect_vec(), + .map(|part| ContentHunk(part.to_vec())), )); } } @@ -224,7 +222,7 @@ mod tests { } fn merge(removes: &[&[u8]], adds: &[&[u8]]) -> MergeResult { - super::merge(&Merge::new(removes.to_vec(), adds.to_vec())) + super::merge(&Merge::from_removes_adds(removes.to_vec(), adds.to_vec())) } #[test] @@ -269,7 +267,7 @@ mod tests { // One side modified, two sides added assert_eq!( merge(&[b"a", b""], &[b"b", b"b", b"b"]), - MergeResult::Conflict(vec![Merge::new( + MergeResult::Conflict(vec![Merge::from_removes_adds( vec![hunk(b"a"), hunk(b"")], vec![hunk(b"b"), hunk(b"b"), hunk(b"b")] )]) @@ -282,7 +280,7 @@ mod tests { // One side modified, two sides removed assert_eq!( merge(&[b"a\n", b"a\n"], &[b"b\n", b"", b""]), - MergeResult::Conflict(vec![Merge::new( + MergeResult::Conflict(vec![Merge::from_removes_adds( vec![hunk(b"a\n"), hunk(b"a\n")], vec![hunk(b"b\n"), hunk(b""), hunk(b"")] )]) @@ -295,7 +293,7 @@ mod tests { // One side removed, one side modified assert_eq!( merge(&[b"a\n"], &[b"", b"b\n"]), - MergeResult::Conflict(vec![Merge::new( + MergeResult::Conflict(vec![Merge::from_removes_adds( vec![hunk(b"a\n")], vec![hunk(b""), hunk(b"b\n")] )]) @@ -303,7 +301,7 @@ mod tests { // One side modified, one side removed assert_eq!( merge(&[b"a\n"], &[b"b\n", b""]), - MergeResult::Conflict(vec![Merge::new( + MergeResult::Conflict(vec![Merge::from_removes_adds( vec![hunk(b"a\n")], vec![hunk(b"b\n"), hunk(b"")] )]) @@ -311,7 +309,7 @@ mod tests { // Two sides modified in different ways assert_eq!( merge(&[b"a"], &[b"b", b"c"]), - MergeResult::Conflict(vec![Merge::new( + MergeResult::Conflict(vec![Merge::from_removes_adds( vec![hunk(b"a")], vec![hunk(b"b"), hunk(b"c")] )]) @@ -329,7 +327,7 @@ mod tests { // One side unchanged, two other sides make the different change assert_eq!( merge(&[b"a", b"a"], &[b"b", b"a", b"c"]), - MergeResult::Conflict(vec![Merge::new( + MergeResult::Conflict(vec![Merge::from_removes_adds( vec![hunk(b"a"), hunk(b"a")], vec![hunk(b"b"), hunk(b"a"), hunk(b"c")] )]) @@ -344,7 +342,7 @@ mod tests { // Merge of an unresolved conflict and another branch. assert_eq!( merge(&[b"a", b"b"], &[b"c", b"d", b"e"]), - MergeResult::Conflict(vec![Merge::new( + MergeResult::Conflict(vec![Merge::from_removes_adds( vec![hunk(b"a"), hunk(b"b")], vec![hunk(b"c"), hunk(b"d"), hunk(b"e")] )]) @@ -352,7 +350,7 @@ mod tests { // Two sides made the same change, third side made a different change assert_eq!( merge(&[b"a", b"b"], &[b"c", b"c", b"c"]), - MergeResult::Conflict(vec![Merge::new( + MergeResult::Conflict(vec![Merge::from_removes_adds( vec![hunk(b"a"), hunk(b"b")], vec![hunk(b"c"), hunk(b"c"), hunk(b"c")] )]) @@ -366,7 +364,7 @@ mod tests { merge(&[b"a\n"], &[b"a\nb\n", b"a\nc\n"]), MergeResult::Conflict(vec![ Merge::resolved(hunk(b"a\n")), - Merge::new(vec![hunk(b"")], vec![hunk(b"b\n"), hunk(b"c\n")]) + Merge::from_removes_adds(vec![hunk(b"")], vec![hunk(b"b\n"), hunk(b"c\n")]) ]) ); // Two sides changed different lines: no conflict @@ -379,7 +377,7 @@ mod tests { merge(&[b"a\nb\nc\n"], &[b"a\nb1\nc\n", b"a\nb2\nc\n"]), MergeResult::Conflict(vec![ Merge::resolved(hunk(b"a\n")), - Merge::new(vec![hunk(b"b\n")], vec![hunk(b"b1\n"), hunk(b"b2\n")]), + Merge::from_removes_adds(vec![hunk(b"b\n")], vec![hunk(b"b1\n"), hunk(b"b2\n")]), Merge::resolved(hunk(b"c\n")) ]) ); diff --git a/lib/src/git_backend.rs b/lib/src/git_backend.rs index 656607f10c..1ad9bcef17 100644 --- a/lib/src/git_backend.rs +++ b/lib/src/git_backend.rs @@ -1376,7 +1376,7 @@ mod tests { TreeId::from_bytes(tree_builder.write().unwrap().as_bytes()) }; - let root_tree = Merge::new( + let root_tree = Merge::from_removes_adds( vec![create_tree(0), create_tree(1)], vec![create_tree(2), create_tree(3), create_tree(4)], ); diff --git a/lib/src/merge.rs b/lib/src/merge.rs index cc7d3aca3e..4ec729bb67 100644 --- a/lib/src/merge.rs +++ b/lib/src/merge.rs @@ -21,10 +21,11 @@ use std::fmt::{Debug, Formatter}; use std::hash::Hash; use std::io::Write; use std::iter::zip; +use std::slice; use std::sync::Arc; -use std::{slice, vec}; use itertools::Itertools; +use smallvec::{smallvec_inline, SmallVec}; use crate::backend; use crate::backend::{BackendError, FileId, ObjectId, TreeId, TreeValue}; @@ -115,7 +116,7 @@ where #[derive(PartialEq, Eq, Hash, Clone)] pub struct Merge { /// Alternates between positive and negative terms, starting with positive. - values: Vec, + values: SmallVec<[T; 1]>, } impl Debug for Merge { @@ -131,19 +132,37 @@ impl Debug for Merge { } impl Merge { + /// Creates a `Merge` from the given values, in which positive and negative + /// terms alternate. + pub fn from_vec(values: impl Into>) -> Self { + let values = values.into(); + assert!( + values.len() & 1 != 0, + "must have one more adds than removes" + ); + Merge { values } + } + /// Creates a new merge object from the given removes and adds. - pub fn new(removes: Vec, adds: Vec) -> Self { - // TODO: removes and adds can be just IntoIterator. - // TODO: maybe add constructor that takes single values vec, and rename this - assert_eq!(adds.len(), removes.len() + 1); - let values = itertools::interleave(adds, removes).collect(); + pub fn from_removes_adds( + removes: impl IntoIterator, + adds: impl IntoIterator, + ) -> Self { + let removes = removes.into_iter(); + let mut adds = adds.into_iter(); + let mut values = SmallVec::with_capacity(removes.size_hint().0 * 2 + 1); + values.push(adds.next().expect("must have at least one add")); + for diff in removes.zip_longest(adds) { + let (remove, add) = diff.both().expect("must have one more adds than removes"); + values.extend([remove, add]); + } Merge { values } } /// Creates a `Merge` with a single resolved value. pub fn resolved(value: T) -> Self { Merge { - values: vec![value], + values: smallvec_inline![value], } } @@ -153,16 +172,14 @@ impl Merge { removes: impl IntoIterator, adds: impl IntoIterator, ) -> Merge> { - // TODO: no need to build intermediate removes/adds vecs - 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); + let removes = removes.into_iter(); + let mut adds = adds.into_iter().fuse(); + let mut values = smallvec_inline![adds.next()]; + for diff in removes.zip_longest(adds) { + let (remove, add) = diff.map_any(Some, Some).or_default(); + values.extend([remove, add]); } - while adds.len() < removes.len() + 1 { - adds.push(None); - } - Merge::new(removes, adds) + Merge { values } } /// The removed values, also called negative terms. @@ -321,7 +338,7 @@ impl Merge { /// the checking until after `from_iter()` (to `MergeBuilder::build()`). #[derive(Clone, Debug, PartialEq, Eq)] pub struct MergeBuilder { - values: Vec, + values: SmallVec<[T; 1]>, } impl Default for MergeBuilder { @@ -336,16 +353,13 @@ impl MergeBuilder { /// Requires that exactly one more "adds" than "removes" have been added to /// this builder. pub fn build(self) -> Merge { - assert!(self.values.len() & 1 != 0); - Merge { - values: self.values, - } + Merge::from_vec(self.values) } } impl IntoIterator for Merge { type Item = T; - type IntoIter = vec::IntoIter; + type IntoIter = smallvec::IntoIter<[T; 1]>; fn into_iter(self) -> Self::IntoIter { self.values.into_iter() @@ -599,7 +613,7 @@ mod tests { use super::*; fn c(removes: &[T], adds: &[T]) -> Merge { - Merge::new(removes.to_vec(), adds.to_vec()) + Merge::from_removes_adds(removes.to_vec(), adds.to_vec()) } #[test] @@ -674,39 +688,48 @@ mod tests { 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)])); + test_equivalent( + (vec![], vec![0]), + Merge::from_removes_adds(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)]), + Merge::from_removes_adds(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]), + Merge::from_removes_adds(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)]), + Merge::from_removes_adds(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)]), + Merge::from_removes_adds(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]), + Merge::from_removes_adds(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)); + assert_eq!( + Merge::from_removes_adds(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); + assert_eq!( + Merge::from_removes_adds(vec![0], vec![0, 1]).as_resolved(), + None + ); } #[test] @@ -781,7 +804,7 @@ mod tests { #[test] fn test_merge_invariants() { fn check_invariants(removes: &[u32], adds: &[u32]) { - let merge = Merge::new(removes.to_vec(), adds.to_vec()); + let merge = Merge::from_removes_adds(removes.to_vec(), adds.to_vec()); // `simplify()` is idempotent assert_eq!( merge.clone().simplify().simplify(), diff --git a/lib/src/merged_tree.rs b/lib/src/merged_tree.rs index c98d559d1a..0ace7f7818 100644 --- a/lib/src/merged_tree.rs +++ b/lib/src/merged_tree.rs @@ -107,48 +107,36 @@ impl MergedTree { if conflict_ids.is_empty() { return Ok(MergedTree::resolved(tree)); } - // Find the number of removes in the most complex conflict. We will then - // build `2*num_removes + 1` trees - let mut max_num_removes = 0; + + // Find the number of removes and adds in the most complex conflict. + let mut max_tree_count = 1; let store = tree.store(); let mut conflicts: Vec<(&RepoPath, MergedTreeValue)> = vec![]; for (path, conflict_id) in &conflict_ids { let conflict = store.read_conflict(path, conflict_id)?; - max_num_removes = max(max_num_removes, conflict.removes().len()); + max_tree_count = max(max_tree_count, conflict.iter().len()); conflicts.push((path, conflict)); } - let mut removes = vec![]; - let mut adds = vec![store.tree_builder(tree.id().clone())]; - for _ in 0..max_num_removes { - removes.push(store.tree_builder(tree.id().clone())); - adds.push(store.tree_builder(tree.id().clone())); - } + let mut tree_builders = Vec::new(); + tree_builders.resize_with(max_tree_count, || store.tree_builder(tree.id().clone())); for (path, conflict) in conflicts { - let num_removes = conflict.removes().len(); // If there are fewer terms in this conflict than in some other conflict, we can // add canceling removes and adds of any value. The simplest value is an absent // value, so we use that. - for i in num_removes..max_num_removes { - removes[i].remove(path.clone()); - adds[i + 1].remove(path.clone()); - } - // Now add the terms that were present in the conflict to the appropriate trees. - for (i, term) in conflict.removes().enumerate() { - removes[i].set_or_remove(path.clone(), term.clone()); - } - for (i, term) in conflict.adds().enumerate() { - adds[i].set_or_remove(path.clone(), term.clone()); + let terms_padded = conflict.into_iter().chain(iter::repeat(None)); + for (builder, term) in zip(&mut tree_builders, terms_padded) { + builder.set_or_remove(path.clone(), term); } } - let write_tree = |builder: TreeBuilder| { - let tree_id = builder.write_tree(); - store.get_tree(&RepoPath::root(), &tree_id) - }; - - let removed_trees = removes.into_iter().map(write_tree).try_collect()?; - let added_trees = adds.into_iter().map(write_tree).try_collect()?; - Ok(MergedTree::Merge(Merge::new(removed_trees, added_trees))) + let new_trees: Vec<_> = tree_builders + .into_iter() + .map(|builder| { + let tree_id = builder.write_tree(); + store.get_tree(&RepoPath::root(), &tree_id) + }) + .try_collect()?; + Ok(MergedTree::Merge(Merge::from_vec(new_trees))) } /// This tree's directory @@ -414,10 +402,7 @@ impl MergedTree { MergedTree::Merge(conflict) => Ok(conflict.clone()), } }; - let nested = Merge::new( - vec![to_merge(base)?], - vec![to_merge(self)?, to_merge(other)?], - ); + let nested = Merge::from_vec(vec![to_merge(self)?, to_merge(base)?, to_merge(other)?]); let tree = merge_trees(&nested.flatten().simplify())?; // If the result can be resolved, then `merge_trees()` above would have returned // a resolved merge. However, that function will always preserve the arity of @@ -501,7 +486,7 @@ fn merge_trees(merge: &Merge) -> Result, TreeMergeError> { new_tree.set_or_remove(basename, value); } Err(path_merge) => { - conflicts.push((basename, path_merge)); + conflicts.push((basename, path_merge.into_iter())); } }; } @@ -512,24 +497,16 @@ fn merge_trees(merge: &Merge) -> Result, TreeMergeError> { // For each side of the conflict, overwrite the entries in `new_tree` with the // values from `conflicts`. Entries that are not in `conflicts` will remain // unchanged and will be reused for each side. - let mut tree_removes = vec![]; - for i in 0..merge.removes().len() { - for (basename, path_conflict) in &conflicts { - new_tree.set_or_remove(basename, path_conflict.get_remove(i).unwrap().clone()); + let tree_count = merge.iter().len(); + let mut new_trees = Vec::with_capacity(tree_count); + for _ in 0..tree_count { + for (basename, path_conflict) in &mut conflicts { + new_tree.set_or_remove(basename, path_conflict.next().unwrap()); } let tree = store.write_tree(dir, new_tree.clone())?; - tree_removes.push(tree); + new_trees.push(tree); } - let mut tree_adds = vec![]; - for i in 0..merge.adds().len() { - for (basename, path_conflict) in &conflicts { - new_tree.set_or_remove(basename, path_conflict.get_add(i).unwrap().clone()); - } - let tree = store.write_tree(dir, new_tree.clone())?; - tree_adds.push(tree); - } - - Ok(Merge::new(tree_removes, tree_adds)) + Ok(Merge::from_vec(new_trees)) } } diff --git a/lib/src/refs.rs b/lib/src/refs.rs index 7b033a8c53..effd81a021 100644 --- a/lib/src/refs.rs +++ b/lib/src/refs.rs @@ -93,10 +93,11 @@ pub fn merge_ref_targets( return resolved.clone(); } - let mut merge = Merge::new( - vec![base.as_merge().clone()], - vec![left.as_merge().clone(), right.as_merge().clone()], - ) + let mut merge = Merge::from_vec(vec![ + left.as_merge().clone(), + base.as_merge().clone(), + right.as_merge().clone(), + ]) .flatten() .simplify(); if !merge.is_resolved() { diff --git a/lib/src/simple_op_store.rs b/lib/src/simple_op_store.rs index 429c350fc1..9a5d1225e5 100644 --- a/lib/src/simple_op_store.rs +++ b/lib/src/simple_op_store.rs @@ -505,9 +505,9 @@ fn ref_target_from_proto(maybe_proto: Option crate::protos::op_store::ref_target::Value::Conflict(conflict) => { let term_from_proto = |term: crate::protos::op_store::ref_conflict::Term| term.value.map(CommitId::new); - let removes = conflict.removes.into_iter().map(term_from_proto).collect(); - let adds = conflict.adds.into_iter().map(term_from_proto).collect(); - RefTarget::from_merge(Merge::new(removes, adds)) + let removes = conflict.removes.into_iter().map(term_from_proto); + let adds = conflict.adds.into_iter().map(term_from_proto); + RefTarget::from_merge(Merge::from_removes_adds(removes, adds)) } } } @@ -811,7 +811,7 @@ mod tests { #[test] fn test_ref_target_change_delete_order_roundtrip() { - let target = RefTarget::from_merge(Merge::new( + let target = RefTarget::from_merge(Merge::from_removes_adds( vec![Some(CommitId::from_hex("111111"))], vec![Some(CommitId::from_hex("222222")), None], )); @@ -819,7 +819,7 @@ mod tests { assert_eq!(ref_target_from_proto(maybe_proto), target); // If it were legacy format, order of None entry would be lost. - let target = RefTarget::from_merge(Merge::new( + let target = RefTarget::from_merge(Merge::from_removes_adds( vec![Some(CommitId::from_hex("111111"))], vec![None, Some(CommitId::from_hex("222222"))], )); diff --git a/lib/src/tree.rs b/lib/src/tree.rs index 373438e228..753eb7d3b4 100644 --- a/lib/src/tree.rs +++ b/lib/src/tree.rs @@ -373,10 +373,11 @@ fn merge_tree_value( _ => { // Start by creating a Merge object. Merges can cleanly represent a single // resolved state, the absence of a state, or a conflicted state. - let conflict = Merge::new( - vec![maybe_base.cloned()], - vec![maybe_side1.cloned(), maybe_side2.cloned()], - ); + let conflict = Merge::from_vec(vec![ + maybe_side1.cloned(), + maybe_base.cloned(), + maybe_side2.cloned(), + ]); let filename = dir.join(basename); let expanded = conflict.try_map(|term| match term { Some(TreeValue::Conflict(id)) => store.read_conflict(&filename, id), diff --git a/lib/tests/test_conflicts.rs b/lib/tests/test_conflicts.rs index 0837d63a6e..70b9829649 100644 --- a/lib/tests/test_conflicts.rs +++ b/lib/tests/test_conflicts.rs @@ -64,7 +64,7 @@ line 5 // The left side should come first. The diff should be use the smaller (right) // side, and the left side should be a snapshot. - let conflict = Merge::new( + let conflict = Merge::from_removes_adds( vec![Some(base_id.clone())], vec![Some(left_id.clone()), Some(right_id.clone())], ); @@ -88,7 +88,7 @@ line 5 ); // Swap the positive terms in the conflict. The diff should still use the right // side, but now the right side should come first. - let conflict = Merge::new( + let conflict = Merge::from_removes_adds( vec![Some(base_id.clone())], vec![Some(right_id.clone()), Some(left_id.clone())], ); @@ -157,7 +157,7 @@ line 3 // The order of (a, b, c) should be preserved. For all cases, the "a" side // should be a snapshot. - let conflict = Merge::new( + let conflict = Merge::from_removes_adds( vec![Some(base_id.clone()), Some(base_id.clone())], vec![Some(a_id.clone()), Some(b_id.clone()), Some(c_id.clone())], ); @@ -181,7 +181,7 @@ line 3 line 3 "### ); - let conflict = Merge::new( + let conflict = Merge::from_removes_adds( vec![Some(base_id.clone()), Some(base_id.clone())], vec![Some(c_id.clone()), Some(b_id.clone()), Some(a_id.clone())], ); @@ -205,7 +205,7 @@ line 3 line 3 "### ); - let conflict = Merge::new( + let conflict = Merge::from_removes_adds( vec![Some(base_id.clone()), Some(base_id.clone())], vec![Some(c_id.clone()), Some(a_id.clone()), Some(b_id.clone())], ); @@ -268,7 +268,7 @@ line 5 right ", ); - let conflict = Merge::new( + let conflict = Merge::from_removes_adds( vec![Some(base_id.clone())], vec![Some(left_id.clone()), Some(right_id.clone())], ); @@ -363,7 +363,7 @@ line 5 ); // left modifies a line, right deletes the same line. - let conflict = Merge::new( + let conflict = Merge::from_removes_adds( vec![Some(base_id.clone())], vec![Some(modified_id.clone()), Some(deleted_id.clone())], ); @@ -382,7 +382,7 @@ line 5 ); // right modifies a line, left deletes the same line. - let conflict = Merge::new( + let conflict = Merge::from_removes_adds( vec![Some(base_id.clone())], vec![Some(deleted_id.clone()), Some(modified_id.clone())], ); @@ -401,7 +401,7 @@ line 5 ); // modify/delete conflict at the file level - let conflict = Merge::new( + let conflict = Merge::from_removes_adds( vec![Some(base_id.clone())], vec![Some(modified_id.clone()), None], ); @@ -599,7 +599,7 @@ fn test_update_conflict_from_content() { let base_file_id = testutils::write_file(store, &path, "line 1\nline 2\nline 3\n"); let left_file_id = testutils::write_file(store, &path, "left 1\nline 2\nleft 3\n"); let right_file_id = testutils::write_file(store, &path, "right 1\nline 2\nright 3\n"); - let conflict = Merge::new( + let conflict = Merge::from_removes_adds( vec![Some(base_file_id.clone())], vec![Some(left_file_id.clone()), Some(right_file_id.clone())], ); @@ -632,7 +632,7 @@ fn test_update_conflict_from_content() { let new_right_file_id = testutils::write_file(store, &path, "resolved 1\nline 2\nright 3\n"); assert_eq!( new_conflict, - Merge::new( + Merge::from_removes_adds( vec![Some(new_base_file_id.clone())], vec![ Some(new_left_file_id.clone()), @@ -650,7 +650,8 @@ fn test_update_conflict_from_content_modify_delete() { let path = RepoPath::from_internal_string("dir/file"); let before_file_id = testutils::write_file(store, &path, "line 1\nline 2 before\nline 3\n"); let after_file_id = testutils::write_file(store, &path, "line 1\nline 2 after\nline 3\n"); - let conflict = Merge::new(vec![Some(before_file_id)], vec![Some(after_file_id), None]); + let conflict = + Merge::from_removes_adds(vec![Some(before_file_id)], vec![Some(after_file_id), None]); // If the content is unchanged compared to the materialized value, we get the // old conflict id back. @@ -677,7 +678,7 @@ fn test_update_conflict_from_content_modify_delete() { assert_eq!( new_conflict, - Merge::new( + Merge::from_removes_adds( vec![Some(new_base_file_id.clone())], vec![Some(new_left_file_id.clone()), None] ) diff --git a/lib/tests/test_local_working_copy.rs b/lib/tests/test_local_working_copy.rs index 928b02f8a4..9e66e3caff 100644 --- a/lib/tests/test_local_working_copy.rs +++ b/lib/tests/test_local_working_copy.rs @@ -122,7 +122,7 @@ fn test_checkout_file_transitions(backend: TestRepoBackend) { let base_file_id = testutils::write_file(store, path, "base file contents"); let left_file_id = testutils::write_file(store, path, "left file contents"); let right_file_id = testutils::write_file(store, path, "right file contents"); - Merge::new( + Merge::from_removes_adds( vec![Some(TreeValue::File { id: base_file_id, executable: false, diff --git a/lib/tests/test_merged_tree.rs b/lib/tests/test_merged_tree.rs index b3a52deb1b..5fc9a1c134 100644 --- a/lib/tests/test_merged_tree.rs +++ b/lib/tests/test_merged_tree.rs @@ -66,7 +66,7 @@ fn test_from_legacy_tree() { let file2_v1_id = write_file(store.as_ref(), &file2_path, "file2_v1"); let file2_v2_id = write_file(store.as_ref(), &file2_path, "file2_v2"); let file2_v3_id = write_file(store.as_ref(), &file2_path, "file2_v3"); - let file2_conflict = Merge::new( + let file2_conflict = Merge::from_removes_adds( vec![Some(file_value(&file2_v1_id))], vec![ Some(file_value(&file2_v2_id)), @@ -80,7 +80,7 @@ fn test_from_legacy_tree() { let file3_path = RepoPath::from_internal_string("modify_delete"); let file3_v1_id = write_file(store.as_ref(), &file3_path, "file3_v1"); let file3_v2_id = write_file(store.as_ref(), &file3_path, "file3_v2"); - let file3_conflict = Merge::new( + let file3_conflict = Merge::from_removes_adds( vec![Some(file_value(&file3_v1_id))], vec![Some(file_value(&file3_v2_id)), None], ); @@ -91,7 +91,7 @@ fn test_from_legacy_tree() { let file4_path = RepoPath::from_internal_string("add_add"); let file4_v1_id = write_file(store.as_ref(), &file4_path, "file4_v1"); let file4_v2_id = write_file(store.as_ref(), &file4_path, "file4_v2"); - let file4_conflict = Merge::new( + let file4_conflict = Merge::from_removes_adds( vec![None], vec![ Some(file_value(&file4_v1_id)), @@ -108,7 +108,7 @@ fn test_from_legacy_tree() { let file5_v3_id = write_file(store.as_ref(), &file5_path, "file5_v3"); let file5_v4_id = write_file(store.as_ref(), &file5_path, "file5_v4"); let file5_v5_id = write_file(store.as_ref(), &file5_path, "file5_v5"); - let file5_conflict = Merge::new( + let file5_conflict = Merge::from_removes_adds( vec![ Some(file_value(&file5_v1_id)), Some(file_value(&file5_v2_id)), @@ -149,7 +149,7 @@ fn test_from_legacy_tree() { // file2: 3-way conflict assert_eq!( merged_tree.value(&file2_path.components()[0]), - MergedTreeVal::Conflict(Merge::new( + MergedTreeVal::Conflict(Merge::from_removes_adds( vec![Some(file_value(&file2_v1_id)), None], vec![ Some(file_value(&file2_v2_id)), @@ -161,7 +161,7 @@ fn test_from_legacy_tree() { // file3: modify/delete conflict assert_eq!( merged_tree.value(&file3_path.components()[0]), - MergedTreeVal::Conflict(Merge::new( + MergedTreeVal::Conflict(Merge::from_removes_adds( vec![Some(file_value(&file3_v1_id)), None], vec![Some(file_value(&file3_v2_id)), None, None], )) @@ -169,7 +169,7 @@ fn test_from_legacy_tree() { // file4: add/add conflict assert_eq!( merged_tree.value(&file4_path.components()[0]), - MergedTreeVal::Conflict(Merge::new( + MergedTreeVal::Conflict(Merge::from_removes_adds( vec![None, None], vec![ Some(file_value(&file4_v1_id)), @@ -181,7 +181,7 @@ fn test_from_legacy_tree() { // file5: 5-way conflict assert_eq!( merged_tree.value(&file5_path.components()[0]), - MergedTreeVal::Conflict(Merge::new( + MergedTreeVal::Conflict(Merge::from_removes_adds( vec![ Some(file_value(&file5_v1_id)), Some(file_value(&file5_v2_id)), @@ -279,7 +279,7 @@ fn test_path_value_and_entries() { (&file_dir_conflict_sub_path, "1"), ], ); - let merged_tree = MergedTree::Merge(Merge::new( + let merged_tree = MergedTree::Merge(Merge::from_removes_adds( vec![tree1.clone()], vec![tree2.clone(), tree3.clone()], )); @@ -287,7 +287,7 @@ fn test_path_value_and_entries() { // Get the root tree assert_eq!( merged_tree.path_value(&RepoPath::root()), - Merge::new( + Merge::from_removes_adds( vec![Some(TreeValue::Tree(tree1.id().clone()))], vec![ Some(TreeValue::Tree(tree2.id().clone())), @@ -310,7 +310,7 @@ fn test_path_value_and_entries() { // Get modify/delete conflict (some None values) assert_eq!( merged_tree.path_value(&modify_delete_path), - Merge::new( + Merge::from_removes_adds( vec![tree1.path_value(&modify_delete_path)], vec![tree2.path_value(&modify_delete_path), None] ), @@ -318,7 +318,7 @@ fn test_path_value_and_entries() { // Get file/dir conflict path assert_eq!( merged_tree.path_value(&file_dir_conflict_path), - Merge::new( + Merge::from_removes_adds( vec![tree1.path_value(&file_dir_conflict_path)], vec![ tree2.path_value(&file_dir_conflict_path), @@ -419,7 +419,7 @@ fn test_resolve_success() { ], ); - let tree = MergedTree::new(Merge::new(vec![base1], vec![side1, side2])); + let tree = MergedTree::new(Merge::from_removes_adds(vec![base1], vec![side1, side2])); let resolved = tree.resolve().unwrap(); let resolved_tree = resolved.as_resolved().unwrap().clone(); assert_eq!( @@ -443,7 +443,7 @@ fn test_resolve_root_becomes_empty() { let side1 = create_single_tree(repo, &[(&path2, "base1")]); let side2 = create_single_tree(repo, &[(&path1, "base1")]); - let tree = MergedTree::new(Merge::new(vec![base1], vec![side1, side2])); + let tree = MergedTree::new(Merge::from_removes_adds(vec![base1], vec![side1, side2])); let resolved = tree.resolve().unwrap(); assert_eq!(resolved.as_resolved().unwrap().id(), store.empty_tree_id()); } @@ -467,11 +467,11 @@ fn test_resolve_with_conflict() { let expected_side2 = create_single_tree(repo, &[(&trivial_path, "side1"), (&conflict_path, "side2")]); - let tree = MergedTree::new(Merge::new(vec![base1], vec![side1, side2])); + let tree = MergedTree::new(Merge::from_removes_adds(vec![base1], vec![side1, side2])); let resolved_tree = tree.resolve().unwrap(); assert_eq!( resolved_tree, - Merge::new(vec![expected_base1], vec![expected_side1, expected_side2]) + Merge::from_removes_adds(vec![expected_base1], vec![expected_side1, expected_side2]) ) } @@ -548,13 +548,13 @@ fn test_conflict_iterator() { ], ); - let tree = MergedTree::new(Merge::new( + let tree = MergedTree::new(Merge::from_removes_adds( vec![base1.clone()], vec![side1.clone(), side2.clone()], )); let conflicts = tree.conflicts().collect_vec(); let conflict_at = |path: &RepoPath| { - Merge::new( + Merge::from_removes_adds( vec![base1.path_value(path)], vec![side1.path_value(path), side2.path_value(path)], ) @@ -619,13 +619,13 @@ fn test_conflict_iterator_higher_arity() { &[(&two_sided_path, "side3"), (&three_sided_path, "side3")], ); - let tree = MergedTree::new(Merge::new( + let tree = MergedTree::new(Merge::from_removes_adds( vec![base1.clone(), base2.clone()], vec![side1.clone(), side2.clone(), side3.clone()], )); let conflicts = tree.conflicts().collect_vec(); let conflict_at = |path: &RepoPath| { - Merge::new( + Merge::from_removes_adds( vec![base1.path_value(path), base2.path_value(path)], vec![ side1.path_value(path), @@ -654,7 +654,7 @@ fn test_conflict_iterator_higher_arity() { vec![ ( two_sided_path.clone(), - Merge::new( + Merge::from_removes_adds( vec![base2.path_value(&two_sided_path)], vec![ side1.path_value(&two_sided_path), @@ -799,11 +799,11 @@ fn test_diff_conflicted() { (&path4, "right-side2"), ], ); - let left_merged = MergedTree::new(Merge::new( + let left_merged = MergedTree::new(Merge::from_removes_adds( vec![left_base.clone()], vec![left_side1.clone(), left_side2.clone()], )); - let right_merged = MergedTree::new(Merge::new( + let right_merged = MergedTree::new(Merge::from_removes_adds( vec![right_base.clone()], vec![right_side1.clone(), right_side2.clone()], )); @@ -926,9 +926,14 @@ fn test_diff_dir_file() { (&path6.join(&file), "right"), ], ); - let left_merged = MergedTree::new(Merge::new(vec![left_base], vec![left_side1, left_side2])); - let right_merged = - MergedTree::new(Merge::new(vec![right_base], vec![right_side1, right_side2])); + let left_merged = MergedTree::new(Merge::from_removes_adds( + vec![left_base], + vec![left_side1, left_side2], + )); + let right_merged = MergedTree::new(Merge::from_removes_adds( + vec![right_base], + vec![right_side1, right_side2], + )); // Test the forwards diff { @@ -1175,7 +1180,7 @@ fn test_merge_partial_resolution() { let base1_merged = MergedTree::new(Merge::resolved(base1)); let side1_merged = MergedTree::new(Merge::resolved(side1)); let side2_merged = MergedTree::new(Merge::resolved(side2)); - let expected_merged = MergedTree::new(Merge::new( + let expected_merged = MergedTree::new(Merge::from_removes_adds( vec![expected_base1], vec![expected_side1, expected_side2], )); @@ -1222,15 +1227,15 @@ fn test_merge_simplify_only() { let tree4 = create_single_tree(repo, &[(&path, "4")]); let tree5 = create_single_tree(repo, &[(&path, "5")]); let expected = tree5.clone(); - let base1_merged = MergedTree::new(Merge::new( + let base1_merged = MergedTree::new(Merge::from_removes_adds( vec![tree1.clone()], vec![tree2.clone(), tree3.clone()], )); - let side1_merged = MergedTree::new(Merge::new( + let side1_merged = MergedTree::new(Merge::from_removes_adds( vec![tree1.clone()], vec![tree4.clone(), tree2.clone()], )); - let side2_merged = MergedTree::new(Merge::new( + let side2_merged = MergedTree::new(Merge::from_removes_adds( vec![tree4.clone()], vec![tree5.clone(), tree3.clone()], )); @@ -1259,13 +1264,13 @@ fn test_merge_simplify_result() { let expected_base1 = create_single_tree(repo, &[(&path1, "1"), (&path2, "3")]); let expected_side1 = create_single_tree(repo, &[(&path1, "2"), (&path2, "3")]); let expected_side2 = create_single_tree(repo, &[(&path1, "3"), (&path2, "3")]); - let side1_merged = MergedTree::new(Merge::new( + let side1_merged = MergedTree::new(Merge::from_removes_adds( vec![tree1.clone()], vec![tree2.clone(), tree3.clone()], )); let base1_merged = MergedTree::new(Merge::resolved(tree4.clone())); let side2_merged = MergedTree::new(Merge::resolved(tree5.clone())); - let expected_merged = MergedTree::new(Merge::new( + let expected_merged = MergedTree::new(Merge::from_removes_adds( vec![expected_base1], vec![expected_side1, expected_side2], )); @@ -1344,7 +1349,7 @@ fn test_merge_simplify_file_conflict() { let parent_base = create_single_tree(repo, &[(&conflict_path, &parent_base_text)]); let parent_left = create_single_tree(repo, &[(&conflict_path, &parent_left_text)]); let parent_right = create_single_tree(repo, &[(&conflict_path, &parent_right_text)]); - let parent_merged = MergedTree::new(Merge::new( + let parent_merged = MergedTree::new(Merge::from_removes_adds( vec![parent_base], vec![parent_left, parent_right], )); @@ -1365,7 +1370,7 @@ fn test_merge_simplify_file_conflict() { (&conflict_path, &child1_right_text), ], ); - let child1_merged = MergedTree::new(Merge::new( + let child1_merged = MergedTree::new(Merge::from_removes_adds( vec![child1_base], vec![child1_left, child1_right], )); @@ -1388,13 +1393,13 @@ fn test_merge_simplify_file_conflict() { // be resolved. If we later change files::merge() so this no longer fails, it // probably means that we can delete this whole test (the Merge::simplify() call // in try_resolve_file_conflict() is just an optimization then). - let text_merge = Merge::new( - vec![Merge::new( + let text_merge = Merge::from_removes_adds( + vec![Merge::from_removes_adds( vec![parent_base_text.as_bytes()], vec![parent_left_text.as_bytes(), parent_right_text.as_bytes()], )], vec![ - Merge::new( + Merge::from_removes_adds( vec![parent_base_text.as_bytes()], vec![parent_left_text.as_bytes(), child1_right_text.as_bytes()], ), diff --git a/lib/tests/test_refs.rs b/lib/tests/test_refs.rs index a6c7ccfabb..ca6dbe3c24 100644 --- a/lib/tests/test_refs.rs +++ b/lib/tests/test_refs.rs @@ -167,19 +167,21 @@ fn test_merge_ref_targets() { // Left removed, right moved forward assert_eq!( merge_ref_targets(index, RefTarget::absent_ref(), &target1, &target3), - RefTarget::from_merge(Merge::new( - vec![Some(commit1.id().clone())], - vec![None, Some(commit3.id().clone())], - )) + RefTarget::from_merge(Merge::from_vec(vec![ + None, + Some(commit1.id().clone()), + Some(commit3.id().clone()), + ])) ); // Right removed, left moved forward assert_eq!( merge_ref_targets(index, &target3, &target1, RefTarget::absent_ref()), - RefTarget::from_merge(Merge::new( - vec![Some(commit1.id().clone())], - vec![Some(commit3.id().clone()), None], - )) + RefTarget::from_merge(Merge::from_vec(vec![ + Some(commit3.id().clone()), + Some(commit1.id().clone()), + None, + ])) ); // Left became conflicted, right moved forward