conflicts: rename Conflict<T> to Merge<T>

Since `Conflict<T>` can also represent a non-conflict state (a single
term), `Merge<T>` seems like better name.

Thanks to @ilyagr for the suggestion in
https://github.com/martinvonz/jj/pull/1774#discussion_r1257547709

Sorry about the churn. It would have been better if I thought of this
name before I introduced `Conflict<T>`.
This commit is contained in:
Martin von Zweigbergk 2023-08-06 06:52:40 -07:00 committed by Martin von Zweigbergk
parent d858db7e85
commit ecc030848d
18 changed files with 269 additions and 274 deletions

View file

@ -33,7 +33,7 @@ use indexmap::{IndexMap, IndexSet};
use itertools::Itertools;
use jj_lib::backend::{CommitId, ObjectId, TreeValue};
use jj_lib::commit::Commit;
use jj_lib::conflicts::Conflict;
use jj_lib::conflicts::Merge;
use jj_lib::dag_walk::topo_order_reverse;
use jj_lib::git_backend::GitBackend;
use jj_lib::matchers::EverythingMatcher;
@ -2703,7 +2703,7 @@ fn cmd_chmod(ui: &mut Ui, command: &CommandHelper, args: &ChmodArgs) -> Result<(
));
}
let new_conflict_id =
store.write_conflict(&repo_path, &Conflict::new(new_removes, new_adds))?;
store.write_conflict(&repo_path, &Merge::new(new_removes, new_adds))?;
TreeValue::Conflict(new_conflict_id)
}
Some(_) => return Err(user_error_with_path("Found neither a file nor a conflict")),
@ -2805,7 +2805,7 @@ fn cmd_resolve(
#[instrument(skip_all)]
fn print_conflicted_paths(
conflicts: &[(RepoPath, Conflict<Option<TreeValue>>)],
conflicts: &[(RepoPath, Merge<Option<TreeValue>>)],
formatter: &mut dyn Formatter,
workspace_command: &WorkspaceCommandHelper,
) -> Result<(), CommandError> {

View file

@ -244,7 +244,7 @@ pub fn run_mergetool(
None => return Err(ConflictResolveError::PathNotFound(repo_path.clone())),
};
let conflict = tree.store().read_conflict(repo_path, &conflict_id)?;
let file_conflict = conflict.to_file_conflict().ok_or_else(|| {
let file_merge = conflict.to_file_merge().ok_or_else(|| {
let mut summary_bytes: Vec<u8> = vec![];
conflict
.describe(&mut summary_bytes)
@ -255,13 +255,13 @@ pub fn run_mergetool(
)
})?;
// We only support conflicts with 2 sides (3-way conflicts)
if file_conflict.adds().len() > 2 {
if file_merge.adds().len() > 2 {
return Err(ConflictResolveError::ConflictTooComplicated {
path: repo_path.clone(),
sides: file_conflict.adds().len(),
sides: file_merge.adds().len(),
});
};
let content = file_conflict.extract_as_single_hunk(tree.store(), repo_path);
let content = file_merge.extract_as_single_hunk(tree.store(), repo_path);
let editor = get_merge_tool_from_settings(ui, settings)?;
let initial_output_content: Vec<u8> = if editor.merge_tool_edits_conflict_markers {

View file

@ -23,7 +23,7 @@ use std::vec::Vec;
use thiserror::Error;
use crate::conflicts;
use crate::conflicts::Merge;
use crate::content_hash::ContentHash;
use crate::repo_path::{RepoPath, RepoPathComponent};
@ -148,7 +148,7 @@ content_hash! {
pub struct Commit {
pub parents: Vec<CommitId>,
pub predecessors: Vec<CommitId>,
pub root_tree: conflicts::Conflict<TreeId>,
pub root_tree: Merge<TreeId>,
/// Indicates that there this commit uses the new tree-level conflict format, which means
/// that if `root_tree` is not a conflict, we know that we won't have to walk it to
/// determine if there are conflicts.
@ -397,7 +397,7 @@ pub fn make_root_commit(root_change_id: ChangeId, empty_tree_id: TreeId) -> Comm
Commit {
parents: vec![],
predecessors: vec![],
root_tree: conflicts::Conflict::resolved(empty_tree_id),
root_tree: Merge::resolved(empty_tree_id),
uses_tree_conflict_format: false,
change_id: root_change_id,
description: String::new(),

View file

@ -18,7 +18,7 @@ use std::sync::Arc;
use crate::backend::{self, BackendResult, ChangeId, CommitId, Signature, TreeId};
use crate::commit::Commit;
use crate::conflicts::Conflict;
use crate::conflicts::Merge;
use crate::repo::{MutableRepo, Repo};
use crate::settings::{JJRng, UserSettings};
@ -45,7 +45,7 @@ impl CommitBuilder<'_> {
parents,
predecessors: vec![],
// TODO(#1624): set this when appropriate
root_tree: Conflict::from_legacy_tree_id(tree_id),
root_tree: Merge::from_legacy_tree_id(tree_id),
uses_tree_conflict_format: false,
change_id,
description: String::new(),
@ -108,7 +108,7 @@ impl CommitBuilder<'_> {
}
pub fn set_tree(mut self, tree_id: TreeId) -> Self {
self.commit.root_tree = Conflict::from_legacy_tree_id(tree_id);
self.commit.root_tree = Merge::from_legacy_tree_id(tree_id);
self
}

View file

@ -37,34 +37,34 @@ const CONFLICT_DIFF_LINE: &[u8] = b"%%%%%%%\n";
const CONFLICT_MINUS_LINE: &[u8] = b"-------\n";
const CONFLICT_PLUS_LINE: &[u8] = b"+++++++\n";
/// A generic representation of conflicting values.
/// A generic representation of merged values.
///
/// There is exactly one more `adds()` than `removes()`. When interpreted as a
/// series of diffs, the conflict's (i+1)-st add is matched with the i-th
/// 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 Conflict<T> {
pub struct Merge<T> {
removes: Vec<T>,
adds: Vec<T>,
}
impl<T> Conflict<T> {
impl<T> Merge<T> {
pub fn new(removes: Vec<T>, adds: Vec<T>) -> Self {
assert_eq!(adds.len(), removes.len() + 1);
Conflict { removes, adds }
Merge { removes, adds }
}
/// Creates a `Conflict` with a single resolved value.
/// Creates a `Merge` with a single resolved value.
pub fn resolved(value: T) -> Self {
Conflict::new(vec![], vec![value])
Merge::new(vec![], vec![value])
}
/// Create a `Conflict` from a `removes` and `adds`, padding with `None` to
/// 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>,
) -> Conflict<Option<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() {
@ -73,7 +73,7 @@ impl<T> Conflict<T> {
while adds.len() < removes.len() + 1 {
adds.push(None);
}
Conflict::new(removes, adds)
Merge::new(removes, adds)
}
/// Returns the removes and adds as a pair.
@ -89,13 +89,13 @@ impl<T> Conflict<T> {
&self.adds
}
/// Whether this conflict is resolved. Does not resolve trivial conflicts.
/// 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 conflict is resolved. Does not
/// resolve trivial conflicts.
/// 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)
@ -104,7 +104,7 @@ impl<T> Conflict<T> {
}
}
/// Simplify the conflict by joining diffs like A->B and B->C into A->C.
/// 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
@ -133,38 +133,35 @@ impl<T> Conflict<T> {
trivial_merge(&self.removes, &self.adds)
}
/// Creates a new conflict by applying `f` to each remove and add.
pub fn map<'a, U>(&'a self, mut f: impl FnMut(&'a T) -> U) -> Conflict<U> {
/// Creates a new merge by applying `f` to each remove and add.
pub fn map<'a, U>(&'a self, mut f: impl FnMut(&'a T) -> U) -> Merge<U> {
self.maybe_map(|term| Some(f(term))).unwrap()
}
/// Creates a new conflict by applying `f` to each remove and add, returning
/// Creates a new merge by applying `f` to each remove and add, returning
/// `None if `f` returns `None` for any of them.
pub fn maybe_map<'a, U>(
&'a self,
mut f: impl FnMut(&'a T) -> Option<U>,
) -> Option<Conflict<U>> {
pub fn maybe_map<'a, U>(&'a self, mut f: impl FnMut(&'a T) -> Option<U>) -> Option<Merge<U>> {
let removes = self.removes.iter().map(&mut f).collect::<Option<_>>()?;
let adds = self.adds.iter().map(&mut f).collect::<Option<_>>()?;
Some(Conflict { removes, adds })
Some(Merge { removes, adds })
}
/// Creates a new conflict by applying `f` to each remove and add, returning
/// 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,
mut f: impl FnMut(&'a T) -> Result<U, E>,
) -> Result<Conflict<U>, E> {
) -> Result<Merge<U>, E> {
let removes = self.removes.iter().map(&mut f).try_collect()?;
let adds = self.adds.iter().map(&mut f).try_collect()?;
Ok(Conflict { removes, adds })
Ok(Merge { removes, adds })
}
}
impl<T> Conflict<Option<T>> {
/// Creates lists of `removes` and `adds` from a `Conflict` by dropping
impl<T> Merge<Option<T>> {
/// 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 `Conflict`.
/// 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(),
@ -173,8 +170,8 @@ impl<T> Conflict<Option<T>> {
}
}
impl<T> Conflict<Conflict<T>> {
/// Flattens a nested conflict into a regular conflict.
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:
///
@ -183,11 +180,11 @@ impl<T> Conflict<Conflict<T>> {
/// 1 2
/// 0
///
/// Flattening that results in this 9-way conflict:
/// Flattening that results in this 9-way merge:
///
/// 4 5 0 7 8
/// 3 2 1 6
pub fn flatten(mut self) -> Conflict<T> {
pub fn flatten(mut self) -> Merge<T> {
self.removes.reverse();
self.adds.reverse();
let mut result = self.adds.pop().unwrap();
@ -207,20 +204,20 @@ impl<T> Conflict<Conflict<T>> {
}
}
impl<T: ContentHash> ContentHash for Conflict<T> {
impl<T: ContentHash> ContentHash for Merge<T> {
fn hash(&self, state: &mut impl digest::Update) {
self.removes().hash(state);
self.adds().hash(state);
}
}
impl Conflict<TreeId> {
// Creates a resolved conflict for a legacy tree id (same as
// `Conflict::resolved()`).
impl Merge<TreeId> {
// Creates a resolved merge for a legacy tree id (same as
// `Merge::resolved()`).
// TODO(#1624): delete when all callers have been updated to support tree-level
// conflicts
pub fn from_legacy_tree_id(value: TreeId) -> Self {
Conflict {
Merge {
removes: vec![],
adds: vec![value],
}
@ -233,18 +230,18 @@ impl Conflict<TreeId> {
}
}
impl Conflict<Option<TreeValue>> {
/// Create a `Conflict` from a `backend::Conflict`, padding with `None` to
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);
Conflict::from_legacy_form(removes, adds)
Merge::from_legacy_form(removes, adds)
}
/// Creates a `backend::Conflict` from a `Conflict` by dropping `None`
/// 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 `Conflict`.
/// 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
@ -264,17 +261,17 @@ impl Conflict<Option<TreeValue>> {
path: &RepoPath,
output: &mut dyn Write,
) -> std::io::Result<()> {
if let Some(file_conflict) = self.to_file_conflict() {
let content = file_conflict.extract_as_single_hunk(store, path);
if let Some(file_merge) = self.to_file_merge() {
let content = file_merge.extract_as_single_hunk(store, path);
materialize_merge_result(&content, output)
} else {
// Unless all terms are regular files, we can't do much better than to try to
// describe the conflict.
// describe the merge.
self.describe(output)
}
}
pub fn to_file_conflict(&self) -> Option<Conflict<Option<FileId>>> {
pub fn to_file_merge(&self) -> Option<Merge<Option<FileId>>> {
self.maybe_map(|term| match term {
None => Some(None),
Some(TreeValue::File {
@ -303,9 +300,9 @@ impl Conflict<Option<TreeValue>> {
store: &Store,
path: &RepoPath,
content: &[u8],
) -> BackendResult<Option<Conflict<Option<TreeValue>>>> {
) -> BackendResult<Option<Merge<Option<TreeValue>>>> {
// TODO: Check that the conflict only involves files and convert it to a
// `Conflict<Option<FileId>>` so we can remove the wildcard pattern in the loops
// `Merge<Option<FileId>>` so we can remove the wildcard pattern in the loops
// further down.
// First check if the new content is unchanged compared to the old content. If
@ -344,7 +341,7 @@ impl Conflict<Option<TreeValue>> {
}
}
// Now write the new files contents we found by parsing the file
// with conflict markers. Update the Conflict object with the new
// with conflict markers. Update the Merge object with the new
// FileIds.
let mut new_removes = vec![];
for (i, buf) in removed_content.iter().enumerate() {
@ -392,30 +389,30 @@ impl Conflict<Option<TreeValue>> {
}
}
}
Ok(Some(Conflict::new(new_removes, new_adds)))
Ok(Some(Merge::new(new_removes, new_adds)))
}
}
impl Conflict<Option<FileId>> {
pub fn extract_as_single_hunk(&self, store: &Store, path: &RepoPath) -> Conflict<ContentHunk> {
impl Merge<Option<FileId>> {
pub fn extract_as_single_hunk(&self, store: &Store, path: &RepoPath) -> Merge<ContentHunk> {
self.map(|term| get_file_contents(store, path, term))
}
}
impl<T> Conflict<Option<T>>
impl<T> Merge<Option<T>>
where
T: Borrow<TreeValue>,
{
/// If every non-`None` term of a `Conflict<Option<TreeValue>>`
/// If every non-`None` term of a `Merge<Option<TreeValue>>`
/// is a `TreeValue::Tree`, this converts it to
/// a `Conflict<Tree>`, with empty trees instead of
/// a `Merge<Tree>`, with empty trees instead of
/// any `None` terms. Otherwise, returns `None`.
pub fn to_tree_conflict(
pub fn to_tree_merge(
&self,
store: &Arc<Store>,
dir: &RepoPath,
) -> Result<Option<Conflict<Tree>>, BackendError> {
let tree_id_conflict = self.maybe_map(|term| match term {
) -> 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() {
@ -425,7 +422,7 @@ where
}
}
});
if let Some(tree_id_conflict) = tree_id_conflict {
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)
@ -433,7 +430,7 @@ where
Ok(Tree::null(store.clone(), dir.clone()))
}
};
Ok(Some(tree_id_conflict.try_map(get_tree)?))
Ok(Some(tree_id_merge.try_map(get_tree)?))
} else {
Ok(None)
}
@ -511,7 +508,7 @@ fn write_diff_hunks(hunks: &[DiffHunk], file: &mut dyn Write) -> std::io::Result
}
pub fn materialize_merge_result(
single_hunk: &Conflict<ContentHunk>,
single_hunk: &Merge<ContentHunk>,
output: &mut dyn Write,
) -> std::io::Result<()> {
let removed_slices = single_hunk
@ -608,7 +605,7 @@ pub fn parse_conflict(
input: &[u8],
num_removes: usize,
num_adds: usize,
) -> Option<Vec<Conflict<ContentHunk>>> {
) -> Option<Vec<Merge<ContentHunk>>> {
if input.is_empty() {
return None;
}
@ -625,7 +622,7 @@ pub fn parse_conflict(
if hunk.removes().len() == num_removes && hunk.adds().len() == num_adds {
let resolved_slice = &input[resolved_start..conflict_start.unwrap()];
if !resolved_slice.is_empty() {
hunks.push(Conflict::resolved(ContentHunk(resolved_slice.to_vec())));
hunks.push(Merge::resolved(ContentHunk(resolved_slice.to_vec())));
}
hunks.push(hunk);
resolved_start = pos + line.len();
@ -639,7 +636,7 @@ pub fn parse_conflict(
None
} else {
if resolved_start < input.len() {
hunks.push(Conflict::resolved(ContentHunk(
hunks.push(Merge::resolved(ContentHunk(
input[resolved_start..].to_vec(),
)));
}
@ -647,7 +644,7 @@ pub fn parse_conflict(
}
}
fn parse_conflict_hunk(input: &[u8]) -> Conflict<ContentHunk> {
fn parse_conflict_hunk(input: &[u8]) -> Merge<ContentHunk> {
enum State {
Diff,
Minus,
@ -688,7 +685,7 @@ fn parse_conflict_hunk(input: &[u8]) -> Conflict<ContentHunk> {
adds.last_mut().unwrap().0.extend_from_slice(rest);
} else {
// Doesn't look like a conflict
return Conflict::resolved(ContentHunk(vec![]));
return Merge::resolved(ContentHunk(vec![]));
}
}
State::Minus => {
@ -699,81 +696,78 @@ fn parse_conflict_hunk(input: &[u8]) -> Conflict<ContentHunk> {
}
State::Unknown => {
// Doesn't look like a conflict
return Conflict::resolved(ContentHunk(vec![]));
return Merge::resolved(ContentHunk(vec![]));
}
}
}
Conflict::new(removes, adds)
Merge::new(removes, adds)
}
#[cfg(test)]
mod tests {
use super::*;
fn c<T: Clone>(removes: &[T], adds: &[T]) -> Conflict<T> {
Conflict::new(removes.to_vec(), adds.to_vec())
fn c<T: Clone>(removes: &[T], adds: &[T]) -> Merge<T> {
Merge::new(removes.to_vec(), adds.to_vec())
}
#[test]
fn test_legacy_form_conversion() {
fn test_equivalent<T>(legacy_form: (Vec<T>, Vec<T>), conflict: Conflict<Option<T>>)
fn test_equivalent<T>(legacy_form: (Vec<T>, Vec<T>), merge: Merge<Option<T>>)
where
T: Clone + PartialEq + std::fmt::Debug,
{
assert_eq!(conflict.clone().into_legacy_form(), legacy_form);
assert_eq!(
Conflict::from_legacy_form(legacy_form.0, legacy_form.1),
conflict
);
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]), Conflict::new(vec![], vec![Some(0)]));
test_equivalent((vec![], vec![0]), Merge::new(vec![], vec![Some(0)]));
// Regular 3-way conflict
test_equivalent(
(vec![0], vec![1, 2]),
Conflict::new(vec![Some(0)], vec![Some(1), Some(2)]),
Merge::new(vec![Some(0)], vec![Some(1), Some(2)]),
);
// Modify/delete conflict
test_equivalent(
(vec![0], vec![1]),
Conflict::new(vec![Some(0)], vec![Some(1), None]),
Merge::new(vec![Some(0)], vec![Some(1), None]),
);
// Add/add conflict
test_equivalent(
(vec![], vec![0, 1]),
Conflict::new(vec![None], vec![Some(0), Some(1)]),
Merge::new(vec![None], vec![Some(0), Some(1)]),
);
// 5-way conflict
test_equivalent(
(vec![0, 1], vec![2, 3, 4]),
Conflict::new(vec![Some(0), Some(1)], vec![Some(2), Some(3), Some(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![]),
Conflict::new(vec![Some(0), Some(1)], vec![None, None, None]),
Merge::new(vec![Some(0), Some(1)], vec![None, None, None]),
);
}
#[test]
fn test_as_resolved() {
assert_eq!(Conflict::new(vec![], vec![0]).as_resolved(), Some(&0));
// Even a trivially resolvable conflict is not resolved
assert_eq!(Conflict::new(vec![0], vec![0, 1]).as_resolved(), None);
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 "conflict"
// 1-way merge
assert_eq!(c(&[], &[0]).simplify(), c(&[], &[0]));
// 3-way conflict
// 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 conflict
// 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]));
@ -833,31 +827,31 @@ mod tests {
}
#[test]
fn test_conflict_invariants() {
fn test_merge_invariants() {
fn check_invariants(removes: &[u32], adds: &[u32]) {
let conflict = Conflict::new(removes.to_vec(), adds.to_vec());
let merge = Merge::new(removes.to_vec(), adds.to_vec());
// `simplify()` is idempotent
assert_eq!(
conflict.clone().simplify().simplify(),
conflict.clone().simplify(),
"simplify() not idempotent for {conflict:?}"
merge.clone().simplify().simplify(),
merge.clone().simplify(),
"simplify() not idempotent for {merge:?}"
);
// `resolve_trivial()` is unaffected by `simplify()`
assert_eq!(
conflict.clone().simplify().resolve_trivial(),
conflict.resolve_trivial(),
"simplify() changed result of resolve_trivial() for {conflict:?}"
merge.clone().simplify().resolve_trivial(),
merge.resolve_trivial(),
"simplify() changed result of resolve_trivial() for {merge:?}"
);
}
// 1-way "conflict"
// 1-way merge
check_invariants(&[], &[0]);
for i in 0..=1 {
for j in 0..=i + 1 {
// 3-way conflict
// 3-way merge
check_invariants(&[0], &[i, j]);
for k in 0..=j + 1 {
for l in 0..=k + 1 {
// 5-way conflict
// 5-way merge
check_invariants(&[0, i], &[j, k, l]);
}
}
@ -870,9 +864,9 @@ mod tests {
fn increment(i: &i32) -> i32 {
i + 1
}
// 1-way conflict
// 1-way merge
assert_eq!(c(&[], &[1]).map(increment), c(&[], &[2]));
// 3-way conflict
// 3-way merge
assert_eq!(c(&[1], &[3, 5]).map(increment), c(&[2], &[4, 6]));
}
@ -885,10 +879,10 @@ mod tests {
None
}
}
// 1-way conflict
// 1-way merge
assert_eq!(c(&[], &[1]).maybe_map(sqrt), Some(c(&[], &[1])));
assert_eq!(c(&[], &[-1]).maybe_map(sqrt), None);
// 3-way conflict
// 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);
@ -903,10 +897,10 @@ mod tests {
Err(())
}
}
// 1-way conflict
// 1-way merge
assert_eq!(c(&[], &[1]).try_map(sqrt), Ok(c(&[], &[1])));
assert_eq!(c(&[], &[-1]).try_map(sqrt), Err(()));
// 3-way conflict
// 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(()));
@ -914,16 +908,16 @@ mod tests {
#[test]
fn test_flatten() {
// 1-way conflict of 1-way conflict
// 1-way merge of 1-way merge
assert_eq!(c(&[], &[c(&[], &[0])]).flatten(), c(&[], &[0]));
// 1-way conflict of 3-way conflict
// 1-way merge of 3-way merge
assert_eq!(c(&[], &[c(&[0], &[1, 2])]).flatten(), c(&[0], &[1, 2]));
// 3-way conflict of 1-way conflicts
// 3-way merge of 1-way merges
assert_eq!(
c(&[c(&[], &[0])], &[c(&[], &[1]), c(&[], &[2])]).flatten(),
c(&[0], &[1, 2])
);
// 3-way conflict of 3-way conflicts
// 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])

View file

@ -20,7 +20,7 @@ use std::ops::Range;
use itertools::Itertools;
use crate::conflicts::Conflict;
use crate::conflicts::Merge;
use crate::diff;
use crate::diff::{Diff, DiffHunk};
use crate::merge::trivial_merge;
@ -157,7 +157,7 @@ impl Debug for ContentHunk {
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum MergeResult {
Resolved(ContentHunk),
Conflict(Vec<Conflict<ContentHunk>>),
Conflict(Vec<Merge<ContentHunk>>),
}
/// A region where the base and two sides match.
@ -179,7 +179,7 @@ pub fn merge(removes: &[&[u8]], adds: &[&[u8]]) -> MergeResult {
let diff = Diff::for_tokenizer(&diff_inputs, &diff::find_line_ranges);
let mut resolved_hunk = ContentHunk(vec![]);
let mut merge_hunks: Vec<Conflict<ContentHunk>> = vec![];
let mut merge_hunks: Vec<Merge<ContentHunk>> = vec![];
for diff_hunk in diff.hunks() {
match diff_hunk {
DiffHunk::Matching(content) => {
@ -190,10 +190,10 @@ pub fn merge(removes: &[&[u8]], adds: &[&[u8]]) -> MergeResult {
resolved_hunk.0.extend(*resolved);
} else {
if !resolved_hunk.0.is_empty() {
merge_hunks.push(Conflict::resolved(resolved_hunk));
merge_hunks.push(Merge::resolved(resolved_hunk));
resolved_hunk = ContentHunk(vec![]);
}
merge_hunks.push(Conflict::new(
merge_hunks.push(Merge::new(
parts[..num_diffs]
.iter()
.map(|part| ContentHunk(part.to_vec()))
@ -212,7 +212,7 @@ pub fn merge(removes: &[&[u8]], adds: &[&[u8]]) -> MergeResult {
MergeResult::Resolved(resolved_hunk)
} else {
if !resolved_hunk.0.is_empty() {
merge_hunks.push(Conflict::resolved(resolved_hunk));
merge_hunks.push(Merge::resolved(resolved_hunk));
}
MergeResult::Conflict(merge_hunks)
}
@ -268,7 +268,7 @@ mod tests {
// One side modified, two sides added
assert_eq!(
merge(&[b"a", b""], &[b"b", b"b", b"b"]),
MergeResult::Conflict(vec![Conflict::new(
MergeResult::Conflict(vec![Merge::new(
vec![hunk(b"a"), hunk(b"")],
vec![hunk(b"b"), hunk(b"b"), hunk(b"b")]
)])
@ -281,7 +281,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![Conflict::new(
MergeResult::Conflict(vec![Merge::new(
vec![hunk(b"a\n"), hunk(b"a\n")],
vec![hunk(b"b\n"), hunk(b""), hunk(b"")]
)])
@ -294,7 +294,7 @@ mod tests {
// One side removed, one side modified
assert_eq!(
merge(&[b"a\n"], &[b"", b"b\n"]),
MergeResult::Conflict(vec![Conflict::new(
MergeResult::Conflict(vec![Merge::new(
vec![hunk(b"a\n")],
vec![hunk(b""), hunk(b"b\n")]
)])
@ -302,7 +302,7 @@ mod tests {
// One side modified, one side removed
assert_eq!(
merge(&[b"a\n"], &[b"b\n", b""]),
MergeResult::Conflict(vec![Conflict::new(
MergeResult::Conflict(vec![Merge::new(
vec![hunk(b"a\n")],
vec![hunk(b"b\n"), hunk(b"")]
)])
@ -310,7 +310,7 @@ mod tests {
// Two sides modified in different ways
assert_eq!(
merge(&[b"a"], &[b"b", b"c"]),
MergeResult::Conflict(vec![Conflict::new(
MergeResult::Conflict(vec![Merge::new(
vec![hunk(b"a")],
vec![hunk(b"b"), hunk(b"c")]
)])
@ -328,7 +328,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![Conflict::new(
MergeResult::Conflict(vec![Merge::new(
vec![hunk(b"a"), hunk(b"a")],
vec![hunk(b"b"), hunk(b"a"), hunk(b"c")]
)])
@ -343,7 +343,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![Conflict::new(
MergeResult::Conflict(vec![Merge::new(
vec![hunk(b"a"), hunk(b"b")],
vec![hunk(b"c"), hunk(b"d"), hunk(b"e")]
)])
@ -351,7 +351,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![Conflict::new(
MergeResult::Conflict(vec![Merge::new(
vec![hunk(b"a"), hunk(b"b")],
vec![hunk(b"c"), hunk(b"c"), hunk(b"c")]
)])
@ -364,8 +364,8 @@ mod tests {
assert_eq!(
merge(&[b"a\n"], &[b"a\nb\n", b"a\nc\n"]),
MergeResult::Conflict(vec![
Conflict::resolved(hunk(b"a\n")),
Conflict::new(vec![hunk(b"")], vec![hunk(b"b\n"), hunk(b"c\n")])
Merge::resolved(hunk(b"a\n")),
Merge::new(vec![hunk(b"")], vec![hunk(b"b\n"), hunk(b"c\n")])
])
);
// Two sides changed different lines: no conflict
@ -377,9 +377,9 @@ mod tests {
assert_eq!(
merge(&[b"a\nb\nc\n"], &[b"a\nb1\nc\n", b"a\nb2\nc\n"]),
MergeResult::Conflict(vec![
Conflict::resolved(hunk(b"a\n")),
Conflict::new(vec![hunk(b"b\n")], vec![hunk(b"b1\n"), hunk(b"b2\n")]),
Conflict::resolved(hunk(b"c\n"))
Merge::resolved(hunk(b"a\n")),
Merge::new(vec![hunk(b"b\n")], vec![hunk(b"b1\n"), hunk(b"b2\n")]),
Merge::resolved(hunk(b"c\n"))
])
);
// One side changes a line and adds a block after. The other side just adds the

View file

@ -32,7 +32,7 @@ use crate::backend::{
ChangeId, Commit, CommitId, Conflict, ConflictId, ConflictTerm, FileId, MillisSinceEpoch,
ObjectId, Signature, SymlinkId, Timestamp, Tree, TreeId, TreeValue,
};
use crate::conflicts;
use crate::conflicts::Merge;
use crate::file_util::{IoResultExt as _, PathError};
use crate::lock::FileLock;
use crate::repo_path::{RepoPath, RepoPathComponent};
@ -223,7 +223,7 @@ fn commit_from_git_without_root_parent(commit: &git2::Commit) -> Commit {
let tree_id = TreeId::from_bytes(commit.tree_id().as_bytes());
// If this commit is a conflict, we'll update the root tree later, when we read
// the extra metadata.
let root_tree = conflicts::Conflict::resolved(tree_id);
let root_tree = Merge::resolved(tree_id);
let description = commit.message().unwrap_or("<no message>").to_owned();
let author = signature_from_git(commit.author());
let committer = signature_from_git(commit.committer());
@ -304,7 +304,7 @@ fn deserialize_extras(commit: &mut Commit, bytes: &[u8]) {
match proto.root_tree {
Some(crate::protos::git_store::commit::RootTree::Conflict(proto_conflict)) => {
assert!(commit.uses_tree_conflict_format);
commit.root_tree = conflicts::Conflict::new(
commit.root_tree = Merge::new(
proto_conflict
.removes
.iter()
@ -765,7 +765,7 @@ impl Backend for GitBackend {
/// `.jjconflict-base-N` subtrees. This ensure that the parts are not GC'd.
fn write_tree_conflict(
repo: &git2::Repository,
conflict: &conflicts::Conflict<TreeId>,
conflict: &Merge<TreeId>,
) -> Result<Oid, BackendError> {
let mut builder = repo.treebuilder(None).unwrap();
let mut add_tree_entry = |name, tree_id: &TreeId| {
@ -988,7 +988,7 @@ mod tests {
let mut commit = Commit {
parents: vec![],
predecessors: vec![],
root_tree: conflicts::Conflict::resolved(backend.empty_tree_id().clone()),
root_tree: Merge::resolved(backend.empty_tree_id().clone()),
uses_tree_conflict_format: false,
change_id: ChangeId::from_hex("abc123"),
description: "".to_string(),
@ -1058,7 +1058,7 @@ mod tests {
TreeId::from_bytes(tree_builder.write().unwrap().as_bytes())
};
let root_tree = conflicts::Conflict::new(
let root_tree = Merge::new(
vec![crete_tree(0), crete_tree(1)],
vec![crete_tree(2), crete_tree(3), crete_tree(4)],
);
@ -1103,7 +1103,7 @@ mod tests {
// When writing a single tree using the new format, it's represented by a
// regular git tree.
commit.root_tree = conflicts::Conflict::resolved(crete_tree(5));
commit.root_tree = Merge::resolved(crete_tree(5));
let read_commit_id = backend.write_commit(commit.clone()).unwrap().0;
let read_commit = backend.read_commit(&read_commit_id).unwrap();
assert_eq!(read_commit, commit);
@ -1131,7 +1131,7 @@ mod tests {
let commit = Commit {
parents: vec![store.root_commit_id().clone()],
predecessors: vec![],
root_tree: conflicts::Conflict::resolved(store.empty_tree_id().clone()),
root_tree: Merge::resolved(store.empty_tree_id().clone()),
uses_tree_conflict_format: false,
change_id: ChangeId::new(vec![]),
description: "initial".to_string(),
@ -1155,7 +1155,7 @@ mod tests {
let mut commit1 = Commit {
parents: vec![store.root_commit_id().clone()],
predecessors: vec![],
root_tree: conflicts::Conflict::resolved(store.empty_tree_id().clone()),
root_tree: Merge::resolved(store.empty_tree_id().clone()),
uses_tree_conflict_format: false,
change_id: ChangeId::new(vec![]),
description: "initial".to_string(),

View file

@ -30,7 +30,7 @@ use crate::backend::{
ConflictId, ConflictTerm, FileId, MillisSinceEpoch, ObjectId, Signature, SymlinkId, Timestamp,
Tree, TreeId, TreeValue,
};
use crate::conflicts;
use crate::conflicts::Merge;
use crate::content_hash::blake2b_hash;
use crate::file_util::persist_content_addressed_temp_file;
use crate::repo_path::{RepoPath, RepoPathComponent};
@ -311,7 +311,7 @@ fn commit_from_proto(proto: crate::protos::local_store::Commit) -> Commit {
let parents = proto.parents.into_iter().map(CommitId::new).collect();
let predecessors = proto.predecessors.into_iter().map(CommitId::new).collect();
let conflict = proto.root_tree.unwrap();
let root_tree = conflicts::Conflict::new(
let root_tree = Merge::new(
conflict.removes.into_iter().map(TreeId::new).collect(),
conflict.adds.into_iter().map(TreeId::new).collect(),
);

View file

@ -22,7 +22,7 @@ use itertools::Itertools;
use crate::backend;
use crate::backend::{ConflictId, TreeValue};
use crate::conflicts::Conflict;
use crate::conflicts::Merge;
use crate::repo_path::{RepoPath, RepoPathComponent, RepoPathJoin};
use crate::store::Store;
use crate::tree::{try_resolve_file_conflict, Tree, TreeMergeError};
@ -35,7 +35,7 @@ pub enum MergedTree {
Legacy(Tree),
/// A merge of multiple trees, or just a single tree. The individual trees
/// have no path-level conflicts.
Merge(Conflict<Tree>),
Merge(Merge<Tree>),
}
/// The value at a given path in a `MergedTree`.
@ -43,20 +43,20 @@ pub enum MergedTree {
pub enum MergedTreeValue<'a> {
/// A single non-conflicted value.
Resolved(Option<&'a TreeValue>),
/// TODO: Make this a `Conflict<Option<&'a TreeValue>>` (reference to the
/// TODO: Make this a `Merge<Option<&'a TreeValue>>` (reference to the
/// value) once we have removed the `MergedTree::Legacy` variant.
Conflict(Conflict<Option<TreeValue>>),
Conflict(Merge<Option<TreeValue>>),
}
impl MergedTree {
/// Creates a new `MergedTree` representing a single tree without conflicts.
pub fn resolved(tree: Tree) -> Self {
MergedTree::new(Conflict::resolved(tree))
MergedTree::new(Merge::resolved(tree))
}
/// Creates a new `MergedTree` representing a merge of a set of trees. The
/// individual trees must not have any conflicts.
pub fn new(conflict: Conflict<Tree>) -> Self {
pub fn new(conflict: Merge<Tree>) -> Self {
debug_assert!(!conflict.removes().iter().any(|t| t.has_conflict()));
debug_assert!(!conflict.adds().iter().any(|t| t.has_conflict()));
debug_assert!(itertools::chain(conflict.removes(), conflict.adds())
@ -85,7 +85,7 @@ impl MergedTree {
// build `2*num_removes + 1` trees
let mut max_num_removes = 0;
let store = tree.store();
let mut conflicts: Vec<(&RepoPath, Conflict<Option<TreeValue>>)> = vec![];
let mut conflicts: Vec<(&RepoPath, Merge<Option<TreeValue>>)> = vec![];
for (path, conflict_id) in &conflict_ids {
let conflict = store.read_conflict(path, conflict_id).unwrap();
max_num_removes = max(max_num_removes, conflict.removes().len());
@ -126,7 +126,7 @@ impl MergedTree {
store.get_tree(&RepoPath::root(), &tree_id).unwrap()
};
MergedTree::Merge(Conflict::new(
MergedTree::Merge(Merge::new(
removes.into_iter().map(write_tree).collect(),
adds.into_iter().map(write_tree).collect(),
))
@ -179,9 +179,9 @@ impl MergedTree {
/// automatically resolved and leaving the rest unresolved. The returned
/// conflict will either be resolved or have the same number of sides as
/// the input.
pub fn resolve(&self) -> Result<Conflict<Tree>, TreeMergeError> {
pub fn resolve(&self) -> Result<Merge<Tree>, TreeMergeError> {
match self {
MergedTree::Legacy(tree) => Ok(Conflict::resolved(tree.clone())),
MergedTree::Legacy(tree) => Ok(Merge::resolved(tree.clone())),
MergedTree::Merge(conflict) => merge_trees(conflict),
}
}
@ -191,7 +191,7 @@ impl MergedTree {
/// all sides are trees, so tree/file conflicts will be reported as a single
/// conflict, not one for each path in the tree.
// TODO: Restrict this by a matcher (or add a separate method for that).
pub fn conflicts(&self) -> impl Iterator<Item = (RepoPath, Conflict<Option<TreeValue>>)> {
pub fn conflicts(&self) -> impl Iterator<Item = (RepoPath, Merge<Option<TreeValue>>)> {
ConflictIterator::new(self.clone())
}
@ -204,19 +204,19 @@ impl MergedTree {
}
}
fn all_tree_conflict_names(conflict: &Conflict<Tree>) -> impl Iterator<Item = &RepoPathComponent> {
fn all_tree_conflict_names(conflict: &Merge<Tree>) -> impl Iterator<Item = &RepoPathComponent> {
itertools::chain(conflict.removes(), conflict.adds())
.map(|tree| tree.data().names())
.kmerge()
.dedup()
}
fn merge_trees(conflict: &Conflict<Tree>) -> Result<Conflict<Tree>, TreeMergeError> {
if let Some(tree) = conflict.resolve_trivial() {
return Ok(Conflict::resolved(tree.clone()));
fn merge_trees(merge: &Merge<Tree>) -> Result<Merge<Tree>, TreeMergeError> {
if let Some(tree) = merge.resolve_trivial() {
return Ok(Merge::resolved(tree.clone()));
}
let base_tree = &conflict.adds()[0];
let base_tree = &merge.adds()[0];
let store = base_tree.store();
let dir = base_tree.dir();
// Keep resolved entries in `new_tree` and conflicted entries in `conflicts` to
@ -224,24 +224,24 @@ fn merge_trees(conflict: &Conflict<Tree>) -> Result<Conflict<Tree>, TreeMergeErr
// any conflicts.
let mut new_tree = backend::Tree::default();
let mut conflicts = vec![];
for basename in all_tree_conflict_names(conflict) {
let path_conflict = conflict.map(|tree| tree.value(basename).cloned());
let path_conflict = merge_tree_values(store, dir, path_conflict)?;
if let Some(value) = path_conflict.as_resolved() {
for basename in all_tree_conflict_names(merge) {
let path_merge = merge.map(|tree| tree.value(basename).cloned());
let path_merge = merge_tree_values(store, dir, path_merge)?;
if let Some(value) = path_merge.as_resolved() {
new_tree.set_or_remove(basename, value.clone());
} else {
conflicts.push((basename, path_conflict));
conflicts.push((basename, path_merge));
};
}
if conflicts.is_empty() {
let new_tree_id = store.write_tree(dir, new_tree)?;
Ok(Conflict::resolved(new_tree_id))
Ok(Merge::resolved(new_tree_id))
} else {
// 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..conflict.removes().len() {
for i in 0..merge.removes().len() {
for (basename, path_conflict) in &conflicts {
new_tree.set_or_remove(basename, path_conflict.removes()[i].clone());
}
@ -249,7 +249,7 @@ fn merge_trees(conflict: &Conflict<Tree>) -> Result<Conflict<Tree>, TreeMergeErr
tree_removes.push(tree);
}
let mut tree_adds = vec![];
for i in 0..conflict.adds().len() {
for i in 0..merge.adds().len() {
for (basename, path_conflict) in &conflicts {
new_tree.set_or_remove(basename, path_conflict.adds()[i].clone());
}
@ -257,7 +257,7 @@ fn merge_trees(conflict: &Conflict<Tree>) -> Result<Conflict<Tree>, TreeMergeErr
tree_adds.push(tree);
}
Ok(Conflict::new(tree_removes, tree_adds))
Ok(Merge::new(tree_removes, tree_adds))
}
}
@ -268,18 +268,18 @@ fn merge_trees(conflict: &Conflict<Tree>) -> Result<Conflict<Tree>, TreeMergeErr
fn merge_tree_values(
store: &Arc<Store>,
path: &RepoPath,
conflict: Conflict<Option<TreeValue>>,
) -> Result<Conflict<Option<TreeValue>>, TreeMergeError> {
conflict: Merge<Option<TreeValue>>,
) -> Result<Merge<Option<TreeValue>>, TreeMergeError> {
if let Some(resolved) = conflict.resolve_trivial() {
return Ok(Conflict::resolved(resolved.clone()));
return Ok(Merge::resolved(resolved.clone()));
}
if let Some(tree_conflict) = conflict.to_tree_conflict(store, path)? {
if let Some(tree_conflict) = conflict.to_tree_merge(store, path)? {
// If all sides are trees or missing, merge the trees recursively, treating
// missing trees as empty.
let merged_tree = merge_trees(&tree_conflict)?;
if merged_tree.as_resolved().map(|tree| tree.id()) == Some(store.empty_tree_id()) {
Ok(Conflict::resolved(None))
Ok(Merge::resolved(None))
} else {
Ok(merged_tree.map(|tree| Some(TreeValue::Tree(tree.id().clone()))))
}
@ -287,7 +287,7 @@ fn merge_tree_values(
// Try to resolve file conflicts by merging the file contents. Treats missing
// files as empty.
if let Some(resolved) = try_resolve_file_conflict(store, path, &conflict)? {
Ok(Conflict::resolved(Some(resolved)))
Ok(Merge::resolved(Some(resolved)))
} else {
// Failed to merge the files, or the paths are not files
Ok(conflict)
@ -325,7 +325,7 @@ impl<'a> ConflictEntriesNonRecursiveIterator<'a> {
}
impl<'a> Iterator for ConflictEntriesNonRecursiveIterator<'a> {
type Item = (&'a RepoPathComponent, Conflict<Option<TreeValue>>);
type Item = (&'a RepoPathComponent, Merge<Option<TreeValue>>);
fn next(&mut self) -> Option<Self::Item> {
for basename in self.basename_iter.by_ref() {
@ -387,7 +387,7 @@ impl ConflictIterator {
}
impl Iterator for ConflictIterator {
type Item = (RepoPath, Conflict<Option<TreeValue>>);
type Item = (RepoPath, Merge<Option<TreeValue>>);
fn next(&mut self) -> Option<Self::Item> {
match self {
@ -409,7 +409,7 @@ impl Iterator for ConflictIterator {
let path = top.tree.dir().join(basename);
// TODO: propagate errors
if let Some(tree_conflict) =
conflict.to_tree_conflict(top.tree.store(), &path).unwrap()
conflict.to_tree_merge(top.tree.store(), &path).unwrap()
{
// If all sides are trees or missing, descend into the merged tree
stack.push(ConflictsDirItem::new(MergedTree::Merge(tree_conflict)));

View file

@ -21,7 +21,7 @@ use once_cell::sync::Lazy;
use thiserror::Error;
use crate::backend::{id_type, CommitId, ObjectId, Timestamp};
use crate::conflicts::Conflict;
use crate::conflicts::Merge;
content_hash! {
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
@ -56,7 +56,7 @@ id_type!(pub OperationId);
content_hash! {
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub struct RefTarget {
conflict: Conflict<Option<CommitId>>,
merge: Merge<Option<CommitId>>,
}
}
@ -69,7 +69,7 @@ impl Default for RefTarget {
impl RefTarget {
/// Creates non-conflicting target pointing to no commit.
pub fn absent() -> Self {
Self::from_conflict(Conflict::resolved(None))
Self::from_merge(Merge::resolved(None))
}
/// Returns non-conflicting target pointing to no commit.
@ -82,7 +82,7 @@ impl RefTarget {
/// Creates non-conflicting target pointing to a commit.
pub fn normal(id: CommitId) -> Self {
Self::from_conflict(Conflict::resolved(Some(id)))
Self::from_merge(Merge::resolved(Some(id)))
}
/// Creates target from removed/added ids.
@ -90,22 +90,22 @@ impl RefTarget {
removed_ids: impl IntoIterator<Item = CommitId>,
added_ids: impl IntoIterator<Item = CommitId>,
) -> Self {
Self::from_conflict(Conflict::from_legacy_form(removed_ids, added_ids))
Self::from_merge(Merge::from_legacy_form(removed_ids, added_ids))
}
pub fn from_conflict(conflict: Conflict<Option<CommitId>>) -> Self {
RefTarget { conflict }
pub fn from_merge(merge: Merge<Option<CommitId>>) -> Self {
RefTarget { merge }
}
/// Returns id if this target is non-conflicting and points to a commit.
pub fn as_normal(&self) -> Option<&CommitId> {
let maybe_id = self.conflict.as_resolved()?;
let maybe_id = self.merge.as_resolved()?;
maybe_id.as_ref()
}
/// Returns true if this target points to no commit.
pub fn is_absent(&self) -> bool {
matches!(self.conflict.as_resolved(), Some(None))
matches!(self.merge.as_resolved(), Some(None))
}
/// Returns true if this target points to any commit. Conflicting target is
@ -116,19 +116,19 @@ impl RefTarget {
/// Whether this target has conflicts.
pub fn has_conflict(&self) -> bool {
!self.conflict.is_resolved()
!self.merge.is_resolved()
}
pub fn removed_ids(&self) -> impl Iterator<Item = &CommitId> {
self.conflict.removes().iter().flatten()
self.merge.removes().iter().flatten()
}
pub fn added_ids(&self) -> impl Iterator<Item = &CommitId> {
self.conflict.adds().iter().flatten()
self.merge.adds().iter().flatten()
}
pub fn as_conflict(&self) -> &Conflict<Option<CommitId>> {
&self.conflict
pub fn as_conflict(&self) -> &Merge<Option<CommitId>> {
&self.merge
}
}

View file

@ -15,7 +15,7 @@
#![allow(missing_docs)]
use crate::backend::CommitId;
use crate::conflicts::Conflict;
use crate::conflicts::Merge;
use crate::index::Index;
use crate::merge::trivial_merge;
use crate::op_store::{BranchTarget, RefTarget, RefTargetOptionExt};
@ -30,31 +30,31 @@ pub fn merge_ref_targets(
return resolved.clone();
}
let conflict = Conflict::new(
let merge = Merge::new(
vec![base.as_conflict().clone()],
vec![left.as_conflict().clone(), right.as_conflict().clone()],
)
.flatten()
.simplify();
if conflict.is_resolved() {
RefTarget::from_conflict(conflict)
if merge.is_resolved() {
RefTarget::from_merge(merge)
} else {
let conflict = merge_ref_targets_non_trivial(index, conflict);
RefTarget::from_conflict(conflict)
let merge = merge_ref_targets_non_trivial(index, merge);
RefTarget::from_merge(merge)
}
}
fn merge_ref_targets_non_trivial(
index: &dyn Index,
conflict: Conflict<Option<CommitId>>,
) -> Conflict<Option<CommitId>> {
conflict: Merge<Option<CommitId>>,
) -> Merge<Option<CommitId>> {
let (mut removes, mut adds) = conflict.take();
while let Some((remove_index, add_index)) = find_pair_to_remove(index, &removes, &adds) {
removes.remove(remove_index);
adds.remove(add_index);
}
Conflict::new(removes, adds)
Merge::new(removes, adds)
}
fn find_pair_to_remove(

View file

@ -25,7 +25,7 @@ use tempfile::{NamedTempFile, PersistError};
use thiserror::Error;
use crate::backend::{CommitId, MillisSinceEpoch, ObjectId, Timestamp};
use crate::conflicts::Conflict;
use crate::conflicts::Merge;
use crate::content_hash::blake2b_hash;
use crate::file_util::persist_content_addressed_temp_file;
use crate::op_store::{
@ -426,7 +426,7 @@ fn ref_target_from_proto(maybe_proto: Option<crate::protos::op_store::RefTarget>
|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_conflict(Conflict::new(removes, adds))
RefTarget::from_merge(Merge::new(removes, adds))
}
}
}
@ -556,7 +556,7 @@ mod tests {
#[test]
fn test_ref_target_change_delete_order_roundtrip() {
let target = RefTarget::from_conflict(Conflict::new(
let target = RefTarget::from_merge(Merge::new(
vec![Some(CommitId::from_hex("111111"))],
vec![Some(CommitId::from_hex("222222")), None],
));
@ -564,7 +564,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_conflict(Conflict::new(
let target = RefTarget::from_merge(Merge::new(
vec![Some(CommitId::from_hex("111111"))],
vec![None, Some(CommitId::from_hex("222222"))],
));

View file

@ -19,14 +19,15 @@ use std::collections::HashMap;
use std::io::Read;
use std::sync::{Arc, RwLock};
use crate::backend;
use crate::backend::{
Backend, BackendResult, ChangeId, CommitId, ConflictId, FileId, SymlinkId, TreeId, TreeValue,
};
use crate::commit::Commit;
use crate::conflicts::Merge;
use crate::repo_path::RepoPath;
use crate::tree::Tree;
use crate::tree_builder::TreeBuilder;
use crate::{backend, conflicts};
/// Wraps the low-level backend and makes it return more convenient types. Also
/// adds caching.
@ -159,15 +160,15 @@ impl Store {
&self,
path: &RepoPath,
id: &ConflictId,
) -> BackendResult<conflicts::Conflict<Option<TreeValue>>> {
) -> BackendResult<Merge<Option<TreeValue>>> {
let backend_conflict = self.backend.read_conflict(path, id)?;
Ok(conflicts::Conflict::from_backend_conflict(backend_conflict))
Ok(Merge::from_backend_conflict(backend_conflict))
}
pub fn write_conflict(
&self,
path: &RepoPath,
contents: &conflicts::Conflict<Option<TreeValue>>,
contents: &Merge<Option<TreeValue>>,
) -> BackendResult<ConflictId> {
self.backend
.write_conflict(path, &contents.clone().into_backend_conflict())

View file

@ -27,7 +27,7 @@ use crate::backend::{
BackendError, ConflictId, FileId, ObjectId, TreeEntriesNonRecursiveIterator, TreeEntry, TreeId,
TreeValue,
};
use crate::conflicts::Conflict;
use crate::conflicts::Merge;
use crate::files::MergeResult;
use crate::matchers::{EverythingMatcher, Matcher};
use crate::merge::trivial_merge;
@ -565,19 +565,19 @@ fn merge_tree_value(
_ => {
// Start by creating a Conflict object. Conflicts can cleanly represent a single
// resolved state, the absence of a state, or a conflicted state.
let conflict = Conflict::new(
let conflict = Merge::new(
vec![maybe_base.cloned()],
vec![maybe_side1.cloned(), maybe_side2.cloned()],
);
let filename = dir.join(basename);
let conflict = simplify_conflict(store, &filename, conflict)?;
if let Some(value) = conflict.as_resolved() {
let merge = simplify_conflict(store, &filename, conflict)?;
if let Some(value) = merge.as_resolved() {
return Ok(value.clone());
}
if let Some(tree_value) = try_resolve_file_conflict(store, &filename, &conflict)? {
if let Some(tree_value) = try_resolve_file_conflict(store, &filename, &merge)? {
Some(tree_value)
} else {
let conflict_id = store.write_conflict(&filename, &conflict)?;
let conflict_id = store.write_conflict(&filename, &merge)?;
Some(TreeValue::Conflict(conflict_id))
}
}
@ -587,7 +587,7 @@ fn merge_tree_value(
pub fn try_resolve_file_conflict(
store: &Store,
filename: &RepoPath,
conflict: &Conflict<Option<TreeValue>>,
conflict: &Merge<Option<TreeValue>>,
) -> Result<Option<TreeValue>, TreeMergeError> {
// If there are any non-file or any missing parts in the conflict, we can't
// merge it. We check early so we don't waste time reading file contents if
@ -657,8 +657,8 @@ pub fn try_resolve_file_conflict(
fn simplify_conflict(
store: &Store,
path: &RepoPath,
conflict: Conflict<Option<TreeValue>>,
) -> Result<Conflict<Option<TreeValue>>, BackendError> {
conflict: Merge<Option<TreeValue>>,
) -> Result<Merge<Option<TreeValue>>, BackendError> {
// Important cases to simplify:
//
// D
@ -692,7 +692,7 @@ fn simplify_conflict(
let expanded = conflict.try_map(|term| match term {
Some(TreeValue::Conflict(id)) => store.read_conflict(path, id),
_ => Ok(Conflict::resolved(term.clone())),
_ => Ok(Merge::resolved(term.clone())),
})?;
Ok(expanded.flatten().simplify())
}

View file

@ -13,7 +13,7 @@
// limitations under the License.
use jj_lib::backend::{FileId, TreeValue};
use jj_lib::conflicts::{parse_conflict, Conflict};
use jj_lib::conflicts::{parse_conflict, Merge};
use jj_lib::repo::Repo;
use jj_lib::repo_path::RepoPath;
use jj_lib::store::Store;
@ -67,7 +67,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 = Conflict::new(
let conflict = Merge::new(
vec![Some(file_value(&base_id))],
vec![Some(file_value(&left_id)), Some(file_value(&right_id))],
);
@ -91,7 +91,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 = Conflict::new(
let conflict = Merge::new(
vec![Some(file_value(&base_id))],
vec![Some(file_value(&right_id)), Some(file_value(&left_id))],
);
@ -160,7 +160,7 @@ line 3
// The order of (a, b, c) should be preserved. For all cases, the "a" side
// should be a snapshot.
let conflict = Conflict::new(
let conflict = Merge::new(
vec![Some(file_value(&base_id)), Some(file_value(&base_id))],
vec![
Some(file_value(&a_id)),
@ -188,7 +188,7 @@ line 3
line 3
"###
);
let conflict = Conflict::new(
let conflict = Merge::new(
vec![Some(file_value(&base_id)), Some(file_value(&base_id))],
vec![
Some(file_value(&c_id)),
@ -216,7 +216,7 @@ line 3
line 3
"###
);
let conflict = Conflict::new(
let conflict = Merge::new(
vec![Some(file_value(&base_id)), Some(file_value(&base_id))],
vec![
Some(file_value(&c_id)),
@ -283,7 +283,7 @@ line 5 right
",
);
let conflict = Conflict::new(
let conflict = Merge::new(
vec![Some(file_value(&base_id))],
vec![Some(file_value(&left_id)), Some(file_value(&right_id))],
);
@ -320,7 +320,7 @@ line 5 right
@r###"
Some(
[
Conflict {
Merge {
removes: [
"line 1\nline 2\n",
],
@ -329,13 +329,13 @@ line 5 right
"line 1 right\nline 2\n",
],
},
Conflict {
Merge {
removes: [],
adds: [
"line 3\n",
],
},
Conflict {
Merge {
removes: [
"line 4\nline 5\n",
],
@ -386,7 +386,7 @@ line 5
);
// left modifies a line, right deletes the same line.
let conflict = Conflict::new(
let conflict = Merge::new(
vec![Some(file_value(&base_id))],
vec![
Some(file_value(&modified_id)),
@ -408,7 +408,7 @@ line 5
);
// right modifies a line, left deletes the same line.
let conflict = Conflict::new(
let conflict = Merge::new(
vec![Some(file_value(&base_id))],
vec![
Some(file_value(&deleted_id)),
@ -430,7 +430,7 @@ line 5
);
// modify/delete conflict at the file level
let conflict = Conflict::new(
let conflict = Merge::new(
vec![Some(file_value(&base_id))],
vec![Some(file_value(&modified_id)), None],
);
@ -488,13 +488,13 @@ line 5
@r###"
Some(
[
Conflict {
Merge {
removes: [],
adds: [
"line 1\n",
],
},
Conflict {
Merge {
removes: [
"line 2\nline 3\nline 4\n",
],
@ -503,7 +503,7 @@ line 5
"right\n",
],
},
Conflict {
Merge {
removes: [],
adds: [
"line 5\n",
@ -542,13 +542,13 @@ line 5
@r###"
Some(
[
Conflict {
Merge {
removes: [],
adds: [
"line 1\n",
],
},
Conflict {
Merge {
removes: [
"line 2\nline 3\nline 4\n",
"line 2\nline 3\nline 4\n",
@ -559,7 +559,7 @@ line 5
"line 2\nforward\nline 3\nline 4\n",
],
},
Conflict {
Merge {
removes: [],
adds: [
"line 5\n",
@ -650,7 +650,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 = Conflict::new(
let conflict = Merge::new(
vec![Some(file_value(&base_file_id))],
vec![
Some(file_value(&left_file_id)),
@ -691,7 +691,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,
Conflict::new(
Merge::new(
vec![Some(file_value(&new_base_file_id))],
vec![
Some(file_value(&new_left_file_id)),
@ -709,7 +709,7 @@ 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 = Conflict::new(
let conflict = Merge::new(
vec![Some(file_value(&before_file_id))],
vec![Some(file_value(&after_file_id)), None],
);
@ -746,7 +746,7 @@ fn test_update_conflict_from_content_modify_delete() {
assert_eq!(
new_conflict,
Conflict::new(
Merge::new(
vec![Some(file_value(&new_base_file_id))],
vec![Some(file_value(&new_left_file_id)), None]
)
@ -756,7 +756,7 @@ fn test_update_conflict_from_content_modify_delete() {
fn materialize_conflict_string(
store: &Store,
path: &RepoPath,
conflict: &Conflict<Option<TreeValue>>,
conflict: &Merge<Option<TreeValue>>,
) -> String {
let mut result: Vec<u8> = vec![];
conflict.materialize(store, path, &mut result).unwrap();

View file

@ -14,7 +14,7 @@
use itertools::Itertools;
use jj_lib::backend::{FileId, TreeValue};
use jj_lib::conflicts::Conflict;
use jj_lib::conflicts::Merge;
use jj_lib::merged_tree::{MergedTree, MergedTreeValue};
use jj_lib::repo::Repo;
use jj_lib::repo_path::{RepoPath, RepoPathComponent, RepoPathJoin};
@ -45,7 +45,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 = Conflict::new(
let file2_conflict = Merge::new(
vec![Some(file_value(&file2_v1_id))],
vec![
Some(file_value(&file2_v2_id)),
@ -59,7 +59,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 = Conflict::new(
let file3_conflict = Merge::new(
vec![Some(file_value(&file3_v1_id))],
vec![Some(file_value(&file3_v2_id)), None],
);
@ -70,7 +70,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 = Conflict::new(
let file4_conflict = Merge::new(
vec![None],
vec![
Some(file_value(&file4_v1_id)),
@ -87,7 +87,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 = Conflict::new(
let file5_conflict = Merge::new(
vec![
Some(file_value(&file5_v1_id)),
Some(file_value(&file5_v2_id)),
@ -130,7 +130,7 @@ fn test_from_legacy_tree() {
// file2: 3-way conflict
assert_eq!(
merged_tree.value(&file2_path.components()[0]),
MergedTreeValue::Conflict(Conflict::new(
MergedTreeValue::Conflict(Merge::new(
vec![Some(file_value(&file2_v1_id)), None],
vec![
Some(file_value(&file2_v2_id)),
@ -142,7 +142,7 @@ fn test_from_legacy_tree() {
// file3: modify/delete conflict
assert_eq!(
merged_tree.value(&file3_path.components()[0]),
MergedTreeValue::Conflict(Conflict::new(
MergedTreeValue::Conflict(Merge::new(
vec![Some(file_value(&file3_v1_id)), None],
vec![Some(file_value(&file3_v2_id)), None, None],
))
@ -150,7 +150,7 @@ fn test_from_legacy_tree() {
// file4: add/add conflict
assert_eq!(
merged_tree.value(&file4_path.components()[0]),
MergedTreeValue::Conflict(Conflict::new(
MergedTreeValue::Conflict(Merge::new(
vec![None, None],
vec![
Some(file_value(&file4_v1_id)),
@ -162,7 +162,7 @@ fn test_from_legacy_tree() {
// file5: 5-way conflict
assert_eq!(
merged_tree.value(&file5_path.components()[0]),
MergedTreeValue::Conflict(Conflict::new(
MergedTreeValue::Conflict(Merge::new(
vec![
Some(file_value(&file5_v1_id)),
Some(file_value(&file5_v2_id)),
@ -236,7 +236,7 @@ fn test_resolve_success() {
],
);
let tree = MergedTree::new(Conflict::new(vec![base1], vec![side1, side2]));
let tree = MergedTree::new(Merge::new(vec![base1], vec![side1, side2]));
let resolved = tree.resolve().unwrap();
let resolved_tree = resolved.as_resolved().unwrap().clone();
assert_eq!(
@ -260,7 +260,7 @@ fn test_resolve_root_becomes_empty() {
let side1 = testutils::create_tree(repo, &[(&path2, "base1")]);
let side2 = testutils::create_tree(repo, &[(&path1, "base1")]);
let tree = MergedTree::new(Conflict::new(vec![base1], vec![side1, side2]));
let tree = MergedTree::new(Merge::new(vec![base1], vec![side1, side2]));
let resolved = tree.resolve().unwrap();
assert_eq!(resolved.as_resolved().unwrap().id(), store.empty_tree_id());
}
@ -287,11 +287,11 @@ fn test_resolve_with_conflict() {
let expected_side2 =
testutils::create_tree(repo, &[(&trivial_path, "side1"), (&conflict_path, "side2")]);
let tree = MergedTree::new(Conflict::new(vec![base1], vec![side1, side2]));
let tree = MergedTree::new(Merge::new(vec![base1], vec![side1, side2]));
let resolved_tree = tree.resolve().unwrap();
assert_eq!(
resolved_tree,
Conflict::new(vec![expected_base1], vec![expected_side1, expected_side2])
Merge::new(vec![expected_base1], vec![expected_side1, expected_side2])
)
}
@ -368,13 +368,13 @@ fn test_conflict_iterator() {
],
);
let tree = MergedTree::new(Conflict::new(
let tree = MergedTree::new(Merge::new(
vec![base1.clone()],
vec![side1.clone(), side2.clone()],
));
let conflicts = tree.conflicts().collect_vec();
let conflict_at = |path: &RepoPath| {
Conflict::new(
Merge::new(
vec![base1.path_value(path)],
vec![side1.path_value(path), side2.path_value(path)],
)
@ -439,13 +439,13 @@ fn test_conflict_iterator_higher_arity() {
&[(&two_sided_path, "side3"), (&three_sided_path, "side3")],
);
let tree = MergedTree::new(Conflict::new(
let tree = MergedTree::new(Merge::new(
vec![base1.clone(), base2.clone()],
vec![side1.clone(), side2.clone(), side3.clone()],
));
let conflicts = tree.conflicts().collect_vec();
let conflict_at = |path: &RepoPath| {
Conflict::new(
Merge::new(
vec![base1.path_value(path), base2.path_value(path)],
vec![
side1.path_value(path),
@ -474,7 +474,7 @@ fn test_conflict_iterator_higher_arity() {
vec![
(
two_sided_path.clone(),
Conflict::new(
Merge::new(
vec![base2.path_value(&two_sided_path)],
vec![
side1.path_value(&two_sided_path),

View file

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use jj_lib::conflicts::Conflict;
use jj_lib::conflicts::Merge;
use jj_lib::op_store::RefTarget;
use jj_lib::refs::merge_ref_targets;
use jj_lib::repo::Repo;
@ -167,7 +167,7 @@ fn test_merge_ref_targets() {
// Left removed, right moved forward
assert_eq!(
merge_ref_targets(index, RefTarget::absent_ref(), &target1, &target3),
RefTarget::from_conflict(Conflict::new(
RefTarget::from_merge(Merge::new(
vec![Some(commit1.id().clone())],
vec![None, Some(commit3.id().clone())],
))
@ -176,7 +176,7 @@ fn test_merge_ref_targets() {
// Right removed, left moved forward
assert_eq!(
merge_ref_targets(index, &target3, &target1, RefTarget::absent_ref()),
RefTarget::from_conflict(Conflict::new(
RefTarget::from_merge(Merge::new(
vec![Some(commit1.id().clone())],
vec![Some(commit3.id().clone()), None],
))

View file

@ -22,7 +22,7 @@ use std::sync::Arc;
use itertools::Itertools;
use jj_lib::backend::{ObjectId, TreeId, TreeValue};
use jj_lib::conflicts::Conflict;
use jj_lib::conflicts::Merge;
use jj_lib::fsmonitor::FsmonitorKind;
use jj_lib::op_store::{OperationId, WorkspaceId};
use jj_lib::repo::{ReadonlyRepo, Repo};
@ -117,7 +117,7 @@ fn test_checkout_file_transitions(use_git: bool) {
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");
let conflict = Conflict::new(
let conflict = Merge::new(
vec![Some(TreeValue::File {
id: base_file_id,
executable: false,