From aa2792c5e5d30d887fd1dfc45a0a2c6bcc72f13d Mon Sep 17 00:00:00 2001 From: Martin von Zweigbergk Date: Sat, 13 May 2023 11:16:55 -0700 Subject: [PATCH] merge: add a generic function for resolving trivial N-way merges We already resolve merge conflicts between hunks, trees, and refs, and maybe more. They each have their own code for the handling trivial merges (where the output is equal to one of the inputs). They look surprisingly different. This commit adds a generic function for doing that. Curiously, this new implementation uses implements it in yet another way (basically using a multi-set). --- lib/src/lib.rs | 1 + lib/src/merge.rs | 138 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 lib/src/merge.rs 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); + } +}