diff --git a/lib/src/lib.rs b/lib/src/lib.rs index affc1c073..31ba31ee3 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -37,6 +37,7 @@ pub mod index; pub mod local_backend; pub mod lock; pub mod matchers; +pub mod merge; pub mod nightly_shims; pub mod op_heads_store; pub mod op_store; diff --git a/lib/src/merge.rs b/lib/src/merge.rs new file mode 100644 index 000000000..a6838076c --- /dev/null +++ b/lib/src/merge.rs @@ -0,0 +1,138 @@ +// 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. + +use std::collections::HashMap; +use std::hash::Hash; + +use itertools::Itertools; + +/// Attempt to resolve trivial conflicts between the inputs. There must be +/// exactly one more adds than removes. +pub fn trivial_merge(removes: &[T], adds: &[T]) -> Option +where + T: Eq + Hash + Clone, +{ + assert_eq!( + adds.len(), + removes.len() + 1, + "trivial_merge() requires exactly one more adds than removes" + ); + + // Check if 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. + if removes.iter().all_equal() && adds.iter().all_equal() { + return Some(adds[0].clone()); + } + + // 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. + let mut counts: HashMap = HashMap::new(); + for value in adds.iter() { + counts + .entry(value.clone()) + .and_modify(|e| *e += 1) + .or_insert(1); + } + for value in removes.iter() { + counts + .entry(value.clone()) + .and_modify(|e| *e -= 1) + .or_insert(-1); + } + + // If there is a single value (i.e. key in the map) with a count of 1 left, then + // that is the result. Values with a count of 0 means that they have + // cancelled out, so we skip them. + let x = counts + .iter() + .filter(|&(_, count)| *count != 0) + .collect_vec(); + match x[..] { + [(value, 1)] => Some(value.clone()), + _ => None, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[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]), None); + 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]), None); + assert_eq!(trivial_merge(&[0, 0], &[1, 0, 2]), None); + assert_eq!(trivial_merge(&[0, 0], &[1, 1, 0]), None); + 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]), None); + 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]), None); + 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]), None); + 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]), None); + 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]), None); + 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]), None); + assert_eq!(trivial_merge(&[0, 1], &[2, 1, 3]), None); + assert_eq!(trivial_merge(&[0, 1], &[2, 2, 0]), None); + assert_eq!(trivial_merge(&[0, 1], &[2, 2, 1]), None); + 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); + } +}