diff --git a/CHANGELOG.md b/CHANGELOG.md index 95dfe306315..4ee57927463 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -85,6 +85,9 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). * Revsets now support logical operators in string patterns. +* Conflict labels can now contain more information about where the sides of + the conflict came from. Currently, only some commands add conflict labels. + ### Fixed bugs * `jj metaedit --author-timestamp` twice with the same value no longer diff --git a/cli/src/commands/diffedit.rs b/cli/src/commands/diffedit.rs index 1313695daf5..eaa37bda124 100644 --- a/cli/src/commands/diffedit.rs +++ b/cli/src/commands/diffedit.rs @@ -149,7 +149,7 @@ don't make any changes, then the operation will be aborted.", let base_tree = merge_commit_trees(tx.repo(), base_commits.as_slice()).block_on()?; let tree = target_commit.tree()?; let tree_id = diff_editor.edit([&base_tree, &tree], &matcher, format_instructions)?; - if tree_id == *target_commit.tree_id() { + if !tree_id.has_changes(target_commit.tree_id()) { writeln!(ui.status(), "Nothing changed.")?; } else { tx.repo_mut() diff --git a/cli/src/commands/file/show.rs b/cli/src/commands/file/show.rs index b26ebe82488..92a0f726dd5 100644 --- a/cli/src/commands/file/show.rs +++ b/cli/src/commands/file/show.rs @@ -24,6 +24,7 @@ use jj_lib::conflicts::materialize_tree_value; use jj_lib::file_util::copy_async_to_sync; use jj_lib::fileset::FilePattern; use jj_lib::fileset::FilesetExpression; +use jj_lib::merged_tree::MergedTree; use jj_lib::repo::Repo as _; use jj_lib::repo_path::RepoPath; use pollster::FutureExt as _; @@ -117,7 +118,7 @@ pub(crate) fn cmd_file_show( path: path.to_owned(), value, }; - write_tree_entries(ui, &workspace_command, &template, [Ok(entry)])?; + write_tree_entries(ui, &workspace_command, &template, &tree, [Ok(entry)])?; return Ok(()); } } @@ -128,6 +129,7 @@ pub(crate) fn cmd_file_show( ui, &workspace_command, &template, + &tree, tree.entries_matching(matcher.as_ref()) .map(|(path, value)| Ok((path, value?))) .map_ok(|(path, value)| TreeEntry { path, value }), @@ -152,6 +154,7 @@ fn write_tree_entries( ui: &Ui, workspace_command: &WorkspaceCommandHelper, template: &TemplateRenderer, + tree: &MergedTree, entries: impl IntoIterator>, ) -> Result<(), CommandError> { let repo = workspace_command.repo(); @@ -159,7 +162,8 @@ fn write_tree_entries( let entry = entry?; template.format(&entry, ui.stdout_formatter().as_mut())?; let materialized = - materialize_tree_value(repo.store(), &entry.path, entry.value).block_on()?; + materialize_tree_value(repo.store(), &entry.path, entry.value, tree.labels()) + .block_on()?; match materialized { MaterializedTreeValue::Absent => panic!("absent values should be excluded"), MaterializedTreeValue::AccessDenied(err) => { @@ -178,7 +182,12 @@ fn write_tree_entries( marker_len: None, merge: repo.store().merge_options().clone(), }; - materialize_merge_result(&file.contents, &mut ui.stdout_formatter(), &options)?; + materialize_merge_result( + &file.contents, + &file.labels, + &mut ui.stdout_formatter(), + &options, + )?; } MaterializedTreeValue::OtherConflict { id } => { ui.stdout_formatter().write_all(id.describe().as_bytes())?; diff --git a/cli/src/commands/restore.rs b/cli/src/commands/restore.rs index 5952428f298..62f71c036a8 100644 --- a/cli/src/commands/restore.rs +++ b/cli/src/commands/restore.rs @@ -164,7 +164,7 @@ pub(crate) fn cmd_restore( }; let new_tree_id = diff_selector.select([&to_tree, &from_tree], &matcher, format_instructions)?; - if &new_tree_id == to_commit.tree_id() { + if !new_tree_id.has_changes(to_commit.tree_id()) { writeln!(ui.status(), "Nothing changed.")?; } else { let mut tx = workspace_command.start_transaction(); diff --git a/cli/src/commands/revert.rs b/cli/src/commands/revert.rs index 875e7c500e4..9236516ec41 100644 --- a/cli/src/commands/revert.rs +++ b/cli/src/commands/revert.rs @@ -143,7 +143,9 @@ pub(crate) fn cmd_revert( { let old_base_tree = commit_to_revert.parent_tree(tx.repo())?; let old_tree = commit_to_revert.tree()?; - let new_tree = new_base_tree.merge(old_tree, old_base_tree).block_on()?; + let new_tree = new_base_tree + .merge_unlabeled(old_tree, old_base_tree) + .block_on()?; let new_parent_ids = parent_ids.clone(); let new_commit = tx .repo_mut() diff --git a/cli/src/commands/split.rs b/cli/src/commands/split.rs index 8b1bf679a3c..a36f68a5e12 100644 --- a/cli/src/commands/split.rs +++ b/cli/src/commands/split.rs @@ -264,7 +264,7 @@ pub(crate) fn cmd_split( // containing the user selected changes as the base for the merge. // This results in a tree with the changes the user didn't select. target_tree - .merge(target.selected_tree.clone(), target.parent_tree.clone()) + .merge_unlabeled(target.selected_tree.clone(), target.parent_tree.clone()) .block_on()? } else { target_tree diff --git a/cli/src/commit_templater.rs b/cli/src/commit_templater.rs index 356e4c6e31b..d2c6718db23 100644 --- a/cli/src/commit_templater.rs +++ b/cli/src/commit_templater.rs @@ -2146,7 +2146,12 @@ impl TreeDiff { fn into_formatted(self, show: F) -> TreeDiffFormatted where - F: Fn(&mut dyn Formatter, &Store, BoxStream) -> Result<(), E>, + F: Fn( + &mut dyn Formatter, + &Store, + Diff<&MergedTree>, + BoxStream, + ) -> Result<(), E>, E: Into, { TreeDiffFormatted { diff: self, show } @@ -2161,14 +2166,21 @@ struct TreeDiffFormatted { impl Template for TreeDiffFormatted where - F: Fn(&mut dyn Formatter, &Store, BoxStream) -> Result<(), E>, + F: Fn( + &mut dyn Formatter, + &Store, + Diff<&MergedTree>, + BoxStream, + ) -> Result<(), E>, E: Into, { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { let show = &self.show; let store = self.diff.from_tree.store(); + let trees = Diff::new(&self.diff.from_tree, &self.diff.to_tree); let tree_diff = self.diff.diff_stream(); - show(formatter.as_mut(), store, tree_diff).or_else(|err| formatter.handle_error(err.into())) + show(formatter.as_mut(), store, trees, tree_diff) + .or_else(|err| formatter.handle_error(err.into())) } } @@ -2214,10 +2226,11 @@ fn builtin_tree_diff_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, T if let Some(context) = context { options.context = context; } - diff.into_formatted(move |formatter, store, tree_diff| { + diff.into_formatted(move |formatter, store, trees, tree_diff| { diff_util::show_color_words_diff( formatter, store, + trees, tree_diff, path_converter, &options, @@ -2256,10 +2269,11 @@ fn builtin_tree_diff_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, T if let Some(context) = context { options.context = context; } - diff.into_formatted(move |formatter, store, tree_diff| { + diff.into_formatted(move |formatter, store, trees, tree_diff| { diff_util::show_git_diff( formatter, store, + trees, tree_diff, &options, conflict_marker_style, @@ -2312,7 +2326,7 @@ fn builtin_tree_diff_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, T let path_converter = language.path_converter; let template = self_property .map(move |diff| { - diff.into_formatted(move |formatter, _store, tree_diff| { + diff.into_formatted(move |formatter, _store, _trees, tree_diff| { diff_util::show_diff_summary(formatter, tree_diff, path_converter) .block_on() }) diff --git a/cli/src/diff_util.rs b/cli/src/diff_util.rs index dc99b0eda79..7ddcf5a5552 100644 --- a/cli/src/diff_util.rs +++ b/cli/src/diff_util.rs @@ -35,6 +35,7 @@ use jj_lib::backend::CopyRecord; use jj_lib::backend::TreeValue; use jj_lib::commit::Commit; use jj_lib::config::ConfigGetError; +use jj_lib::conflict_labels::ConflictLabels; use jj_lib::conflicts::ConflictMarkerStyle; use jj_lib::conflicts::ConflictMaterializeOptions; use jj_lib::conflicts::MaterializedTreeDiffEntry; @@ -463,6 +464,7 @@ impl<'a> DiffRenderer<'a> { width: usize, ) -> Result<(), DiffRenderError> { let store = self.repo.store(); + let trees = Diff::new(from_tree, to_tree); let path_converter = self.path_converter; for format in &self.formats { match format { @@ -495,6 +497,7 @@ impl<'a> DiffRenderer<'a> { show_git_diff( formatter, store, + trees, tree_diff, options, self.conflict_marker_style, @@ -507,6 +510,7 @@ impl<'a> DiffRenderer<'a> { show_color_words_diff( formatter, store, + trees, tree_diff, path_converter, options, @@ -523,6 +527,7 @@ impl<'a> DiffRenderer<'a> { ui, formatter, store, + trees, tree_diff, path_converter, tool, @@ -587,7 +592,8 @@ impl<'a> DiffRenderer<'a> { writeln!(formatter.labeled("header"), "Modified commit description:")?; show_color_words_diff_hunks( formatter, - [from_description, to_description], + Diff::new(from_description, to_description) + .map(|description| (description, ConflictLabels::unlabeled())), options, &materialize_options, )?; @@ -753,10 +759,14 @@ impl ColorWordsDiffOptions { fn show_color_words_diff_hunks>( formatter: &mut dyn Formatter, - [lefts, rights]: [&Merge; 2], + contents: Diff<(&Merge, ConflictLabels)>, options: &ColorWordsDiffOptions, materialize_options: &ConflictMaterializeOptions, ) -> io::Result<()> { + let Diff { + before: (lefts, left_labels), + after: (rights, right_labels), + } = contents; let line_number = DiffLineNumber { left: 1, right: 1 }; let labels = ["removed", "added"]; if let (Some(left), Some(right)) = (lefts.as_resolved(), rights.as_resolved()) { @@ -766,8 +776,9 @@ fn show_color_words_diff_hunks>( } match options.conflict { ConflictDiffMethod::Materialize => { - let left = materialize_merge_result_to_bytes(lefts, materialize_options); - let right = materialize_merge_result_to_bytes(rights, materialize_options); + let left = materialize_merge_result_to_bytes(lefts, &left_labels, materialize_options); + let right = + materialize_merge_result_to_bytes(rights, &right_labels, materialize_options); let contents = [&left, &right].map(BStr::new); show_color_words_resolved_hunks(formatter, contents, line_number, labels, options)?; } @@ -1204,22 +1215,29 @@ fn diff_content( path, value, |content| content, - |contents| materialize_merge_result_to_bytes(&contents, materialize_options), + |contents, labels| { + materialize_merge_result_to_bytes(&contents, &labels, materialize_options) + }, ) } fn diff_content_as_merge( path: &RepoPath, value: MaterializedTreeValue, -) -> BackendResult>> { - diff_content_with(path, value, Merge::resolved, |contents| contents) +) -> BackendResult, ConflictLabels)>> { + diff_content_with( + path, + value, + |contents| (Merge::resolved(contents), ConflictLabels::unlabeled()), + |contents, labels| (contents, labels), + ) } fn diff_content_with( path: &RepoPath, value: MaterializedTreeValue, map_resolved: impl FnOnce(BString) -> T, - map_conflict: impl FnOnce(Merge) -> T, + map_conflict: impl FnOnce(Merge, ConflictLabels) -> T, ) -> BackendResult> { match value { MaterializedTreeValue::Absent => Ok(FileContent { @@ -1245,7 +1263,7 @@ fn diff_content_with( // TODO: are we sure this is never binary? MaterializedTreeValue::FileConflict(file) => Ok(FileContent { is_binary: false, - contents: map_conflict(file.contents), + contents: map_conflict(file.contents, file.labels), }), MaterializedTreeValue::OtherConflict { id } => Ok(FileContent { is_binary: false, @@ -1282,6 +1300,7 @@ fn basic_diff_file_type(value: &MaterializedTreeValue) -> &'static str { pub async fn show_color_words_diff( formatter: &mut dyn Formatter, store: &Store, + trees: Diff<&MergedTree>, tree_diff: BoxStream<'_, CopiesTreeDiffEntry>, path_converter: &RepoPathUiConverter, options: &ColorWordsDiffOptions, @@ -1292,8 +1311,9 @@ pub async fn show_color_words_diff( marker_len: None, merge: store.merge_options().clone(), }; + let conflict_labels = trees.map(|tree| tree.labels()); let empty_content = || Merge::resolved(BString::default()); - let mut diff_stream = materialized_diff_stream(store, tree_diff); + let mut diff_stream = materialized_diff_stream(store, tree_diff, conflict_labels); while let Some(MaterializedTreeDiffEntry { path, values }) = diff_stream.next().await { let left_path = path.source(); let right_path = path.target(); @@ -1334,7 +1354,10 @@ pub async fn show_color_words_diff( } else { show_color_words_diff_hunks( formatter, - [&empty_content(), &right_content.contents], + Diff::new( + (&empty_content(), ConflictLabels::unlabeled()), + (&right_content.contents.0, right_content.contents.1), + ), options, &materialize_options, )?; @@ -1401,7 +1424,10 @@ pub async fn show_color_words_diff( } else if left_content.contents != right_content.contents { show_color_words_diff_hunks( formatter, - [&left_content.contents, &right_content.contents], + Diff::new( + (&left_content.contents.0, left_content.contents.1), + (&right_content.contents.0, right_content.contents.1), + ), options, &materialize_options, )?; @@ -1420,7 +1446,10 @@ pub async fn show_color_words_diff( } else { show_color_words_diff_hunks( formatter, - [&left_content.contents, &empty_content()], + Diff::new( + (&left_content.contents.0, left_content.contents.1), + (&empty_content(), ConflictLabels::unlabeled()), + ), options, &materialize_options, )?; @@ -1435,6 +1464,7 @@ pub async fn show_file_by_file_diff( ui: &Ui, formatter: &mut dyn Formatter, store: &Store, + trees: Diff<&MergedTree>, tree_diff: BoxStream<'_, CopiesTreeDiffEntry>, path_converter: &RepoPathUiConverter, tool: &ExternalMergeTool, @@ -1457,10 +1487,11 @@ pub async fn show_file_by_file_diff( Ok(fs_path) }; + let conflict_labels = trees.map(|tree| tree.labels()); let temp_dir = new_utf8_temp_dir("jj-diff-")?; let left_wc_dir = temp_dir.path().join("left"); let right_wc_dir = temp_dir.path().join("right"); - let mut diff_stream = materialized_diff_stream(store, tree_diff); + let mut diff_stream = materialized_diff_stream(store, tree_diff, conflict_labels); while let Some(MaterializedTreeDiffEntry { path, values }) = diff_stream.next().await { let (left_value, right_value) = values?; let left_path = path.source(); @@ -1598,6 +1629,7 @@ fn show_diff_line_tokens( pub async fn show_git_diff( formatter: &mut dyn Formatter, store: &Store, + trees: Diff<&MergedTree>, tree_diff: BoxStream<'_, CopiesTreeDiffEntry>, options: &UnifiedDiffOptions, marker_style: ConflictMarkerStyle, @@ -1607,7 +1639,8 @@ pub async fn show_git_diff( marker_len: None, merge: store.merge_options().clone(), }; - let mut diff_stream = materialized_diff_stream(store, tree_diff); + let conflict_labels = trees.map(|tree| tree.labels()); + let mut diff_stream = materialized_diff_stream(store, tree_diff, conflict_labels); while let Some(MaterializedTreeDiffEntry { path, values }) = diff_stream.next().await { let left_path = path.source(); let right_path = path.target(); @@ -1708,6 +1741,7 @@ fn show_git_diff_texts>( Some(text) => Cow::Borrowed(BStr::new(text)), None => Cow::Owned(materialize_merge_result_to_bytes( content, + &ConflictLabels::unlabeled(), materialize_options, )), }); @@ -1782,16 +1816,21 @@ impl DiffStats { marker_len: None, merge: store.merge_options().clone(), }; - let entries = materialized_diff_stream(store, tree_diff) - .map(|MaterializedTreeDiffEntry { path, values }| { - let (left, right) = values?; - let left_content = diff_content(path.source(), left, &materialize_options)?; - let right_content = diff_content(path.target(), right, &materialize_options)?; - let stat = get_diff_stat_entry(path, [&left_content, &right_content], options); - BackendResult::Ok(stat) - }) - .try_collect() - .await?; + let conflict_labels = ConflictLabels::unlabeled(); + let entries = materialized_diff_stream( + store, + tree_diff, + Diff::new(&conflict_labels, &conflict_labels), + ) + .map(|MaterializedTreeDiffEntry { path, values }| { + let (left, right) = values?; + let left_content = diff_content(path.source(), left, &materialize_options)?; + let right_content = diff_content(path.target(), right, &materialize_options)?; + let stat = get_diff_stat_entry(path, [&left_content, &right_content], options); + BackendResult::Ok(stat) + }) + .try_collect() + .await?; Ok(Self { entries }) } diff --git a/cli/src/merge_tools/builtin.rs b/cli/src/merge_tools/builtin.rs index b6bdd157a85..8bac3038da6 100644 --- a/cli/src/merge_tools/builtin.rs +++ b/cli/src/merge_tools/builtin.rs @@ -23,6 +23,7 @@ use jj_lib::diff::DiffHunkKind; use jj_lib::files; use jj_lib::files::MergeResult; use jj_lib::matchers::Matcher; +use jj_lib::merge::Diff; use jj_lib::merge::Merge; use jj_lib::merge::MergedTreeValue; use jj_lib::merged_tree::MergedTree; @@ -178,7 +179,12 @@ fn read_file_contents( // Since scm_record doesn't support diffs of conflicts, file // conflicts are compared in materialized form. The UI would look // scary, but it can at least allow squashing resolved hunks. - let buf = materialize_merge_result_to_bytes(&file.contents, materialize_options).into(); + let buf = materialize_merge_result_to_bytes( + &file.contents, + &file.labels, + materialize_options, + ) + .into(); // TODO: Render the ID somehow? let contents = buf_to_file_contents(None, buf); Ok(FileInfo { @@ -262,6 +268,8 @@ fn make_diff_sections( async fn make_diff_files( store: &Arc, + left_tree: &MergedTree, + right_tree: &MergedTree, tree_diff: BoxStream<'_, CopiesTreeDiffEntry>, marker_style: ConflictMarkerStyle, ) -> Result<(Vec, Vec>), BuiltinToolError> { @@ -270,7 +278,8 @@ async fn make_diff_files( marker_len: None, merge: store.merge_options().clone(), }; - let mut diff_stream = materialized_diff_stream(store, tree_diff); + let conflict_labels = Diff::new(left_tree.labels(), right_tree.labels()); + let mut diff_stream = materialized_diff_stream(store, tree_diff, conflict_labels); let mut changed_files = Vec::new(); let mut files = Vec::new(); while let Some(entry) = diff_stream.next().await { @@ -545,8 +554,14 @@ pub fn edit_diff_builtin( // TODO: handle copy tracking let copy_records = CopyRecords::default(); let tree_diff = left_tree.diff_stream_with_copies(right_tree, matcher, ©_records); - let (changed_files, files) = - make_diff_files(&store, tree_diff, conflict_marker_style).block_on()?; + let (changed_files, files) = make_diff_files( + &store, + left_tree, + right_tree, + tree_diff, + conflict_marker_style, + ) + .block_on()?; let mut input = scm_record::helpers::CrosstermInput; let recorder = scm_record::Recorder::new( scm_record::RecordState { @@ -754,9 +769,15 @@ mod tests { ) -> (Vec, Vec>) { let copy_records = CopyRecords::default(); let tree_diff = left_tree.diff_stream_with_copies(right_tree, matcher, ©_records); - make_diff_files(store, tree_diff, ConflictMarkerStyle::Diff) - .block_on() - .unwrap() + make_diff_files( + store, + left_tree, + right_tree, + tree_diff, + ConflictMarkerStyle::Diff, + ) + .block_on() + .unwrap() } fn apply_diff( @@ -1604,7 +1625,7 @@ mod tests { let base = testutils::create_single_tree(&test_repo.repo, &[(file_path, "")]); let left = testutils::create_single_tree(&test_repo.repo, &[(file_path, "1\n")]); let right = testutils::create_single_tree(&test_repo.repo, &[(file_path, "2\n")]); - MergedTree::new(Merge::from_vec(vec![left, base, right])) + MergedTree::unlabeled(Merge::from_vec(vec![left, base, right])) }; let right_tree = testutils::create_tree(&test_repo.repo, &[(file_path, "resolved\n")]); @@ -1628,12 +1649,12 @@ mod tests { SectionChangedLine { is_checked: false, change_type: Removed, - line: "<<<<<<< Conflict 1 of 1\n", + line: "<<<<<<< conflict 1 of 1\n", }, SectionChangedLine { is_checked: false, change_type: Removed, - line: "%%%%%%% Changes from base to side #1\n", + line: "%%%%%%% side #1 compared with base\n", }, SectionChangedLine { is_checked: false, @@ -1643,7 +1664,7 @@ mod tests { SectionChangedLine { is_checked: false, change_type: Removed, - line: "+++++++ Contents of side #2\n", + line: "+++++++ side #2\n", }, SectionChangedLine { is_checked: false, @@ -1653,7 +1674,7 @@ mod tests { SectionChangedLine { is_checked: false, change_type: Removed, - line: ">>>>>>> Conflict 1 of 1 ends\n", + line: ">>>>>>> conflict 1 of 1 ends\n", }, SectionChangedLine { is_checked: false, diff --git a/cli/src/merge_tools/external.rs b/cli/src/merge_tools/external.rs index 2a9c25565d9..f11bfc06570 100644 --- a/cli/src/merge_tools/external.rs +++ b/cli/src/merge_tools/external.rs @@ -211,7 +211,7 @@ fn run_mergetool_external_single_file( marker_len: Some(conflict_marker_len), merge: store.merge_options().clone(), }; - materialize_merge_result_to_bytes(&file.contents, &options) + materialize_merge_result_to_bytes(&file.contents, &file.labels, &options) } else { BString::default() }; diff --git a/cli/src/merge_tools/mod.rs b/cli/src/merge_tools/mod.rs index 45edad7af76..d1bde34fbaf 100644 --- a/cli/src/merge_tools/mod.rs +++ b/cli/src/merge_tools/mod.rs @@ -338,12 +338,13 @@ impl MergeToolFile { Ok(Some(_)) => return Err(ConflictResolveError::NotAConflict(repo_path.to_owned())), Ok(None) => return Err(ConflictResolveError::PathNotFound(repo_path.to_owned())), }; - let file = try_materialize_file_conflict_value(tree.store(), repo_path, &conflict) - .block_on()? - .ok_or_else(|| ConflictResolveError::NotNormalFiles { - path: repo_path.to_owned(), - summary: conflict.describe(), - })?; + let file = + try_materialize_file_conflict_value(tree.store(), repo_path, &conflict, tree.labels()) + .block_on()? + .ok_or_else(|| ConflictResolveError::NotNormalFiles { + path: repo_path.to_owned(), + summary: conflict.describe(), + })?; // We only support conflicts with 2 sides (3-way conflicts) if file.ids.num_sides() > 2 { return Err(ConflictResolveError::ConflictTooComplicated { diff --git a/cli/tests/test_absorb_command.rs b/cli/tests/test_absorb_command.rs index a89f98f3915..99646317dae 100644 --- a/cli/tests/test_absorb_command.rs +++ b/cli/tests/test_absorb_command.rs @@ -199,16 +199,16 @@ fn test_absorb_replace_single_line_hunk() { │ --- a/file1 │ +++ b/file1 │ @@ -1,10 +1,3 @@ - │ -<<<<<<< Conflict 1 of 1 - │ -%%%%%%% Changes from base to side #1 + │ -<<<<<<< conflict 1 of 1 + │ -%%%%%%% side #1 compared with base │ --2a │ - 1a │ --2b - │ -+++++++ Contents of side #2 + │ -+++++++ side #2 │ 2a │ 1A │ 2b - │ ->>>>>>> Conflict 1 of 1 ends + │ ->>>>>>> conflict 1 of 1 ends × qpvuntsm 5bdb5ca1 (conflict) 1 │ diff --git a/file1 b/file1 ~ new file mode 100644 @@ -216,16 +216,16 @@ fn test_absorb_replace_single_line_hunk() { --- /dev/null +++ b/file1 @@ -0,0 +1,10 @@ - +<<<<<<< Conflict 1 of 1 - +%%%%%%% Changes from base to side #1 + +<<<<<<< conflict 1 of 1 + +%%%%%%% side #1 compared with base +-2a + 1a +-2b - ++++++++ Contents of side #2 + ++++++++ side #2 +2a +1A +2b - +>>>>>>> Conflict 1 of 1 ends + +>>>>>>> conflict 1 of 1 ends [EOF] "); } @@ -421,16 +421,16 @@ fn test_absorb_conflict() { work_dir.run_jj(["new", "root()"]).success(); work_dir.write_file("file1", "2a\n2b\n"); let output = work_dir.run_jj(["rebase", "-r@", "-ddescription(1)"]); - insta::assert_snapshot!(output, @r###" + insta::assert_snapshot!(output, @r" ------- stderr ------- Rebased 1 commits to destination - Working copy (@) now at: kkmpptxz 66d44b8c (conflict) (no description set) + Working copy (@) now at: kkmpptxz 1643fc83 (conflict) (no description set) Parent commit (@-) : qpvuntsm e35bcaff 1 Added 0 files, modified 1 files, removed 0 files Warning: There are unresolved conflicts at these paths: file1 2-sided conflict New conflicts appeared in 1 commits: - kkmpptxz 66d44b8c (conflict) (no description set) + kkmpptxz 1643fc83 (conflict) (no description set) Hint: To resolve the conflicts, start by creating a commit on top of the conflicted commit: jj new kkmpptxz @@ -438,18 +438,18 @@ fn test_absorb_conflict() { Once the conflicts are resolved, you can inspect the result with `jj diff`. Then run `jj squash` to move the resolution into the conflicted commit. [EOF] - "###); + "); let conflict_content = work_dir.read_file("file1"); insta::assert_snapshot!(conflict_content, @r" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% rebase destination (qpvuntsm e35bcaff) compared with parents of kkmpptxz e05db987 +1a +1b - +++++++ Contents of side #2 + +++++++ rebased commit (kkmpptxz e05db987) 2a 2b - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends "); // Cannot absorb from conflict @@ -577,13 +577,13 @@ fn test_absorb_deleted_file_with_multiple_hunks() { │ --- a/file2 │ +++ /dev/null │ @@ -1,7 +0,0 @@ - │ -<<<<<<< Conflict 1 of 1 - │ -%%%%%%% Changes from base to side #1 + │ -<<<<<<< conflict 1 of 1 + │ -%%%%%%% side #1 compared with base │ --1a │ - 1b - │ -+++++++ Contents of side #2 + │ -+++++++ side #2 │ -1a - │ ->>>>>>> Conflict 1 of 1 ends + │ ->>>>>>> conflict 1 of 1 ends × kkmpptxz 8407ab95 (conflict) 2 │ diff --git a/file1 b/file1 │ deleted file mode 100644 @@ -591,26 +591,26 @@ fn test_absorb_deleted_file_with_multiple_hunks() { │ --- a/file1 │ +++ /dev/null │ @@ -1,6 +0,0 @@ - │ -<<<<<<< Conflict 1 of 1 - │ -%%%%%%% Changes from base to side #1 + │ -<<<<<<< conflict 1 of 1 + │ -%%%%%%% side #1 compared with base │ - 1a │ -+1b - │ -+++++++ Contents of side #2 - │ ->>>>>>> Conflict 1 of 1 ends + │ -+++++++ side #2 + │ ->>>>>>> conflict 1 of 1 ends │ diff --git a/file2 b/file2 │ --- a/file2 │ +++ b/file2 │ @@ -1,7 +1,7 @@ - │ <<<<<<< Conflict 1 of 1 - │ %%%%%%% Changes from base to side #1 + │ <<<<<<< conflict 1 of 1 + │ %%%%%%% side #1 compared with base │ - 1a │ --1b │ +-1a │ + 1b - │ +++++++ Contents of side #2 + │ +++++++ side #2 │ -1b │ +1a - │ >>>>>>> Conflict 1 of 1 ends + │ >>>>>>> conflict 1 of 1 ends × qpvuntsm f1473264 (conflict) 1 │ diff --git a/file1 b/file1 ~ new file mode 100644 @@ -618,25 +618,25 @@ fn test_absorb_deleted_file_with_multiple_hunks() { --- /dev/null +++ b/file1 @@ -0,0 +1,6 @@ - +<<<<<<< Conflict 1 of 1 - +%%%%%%% Changes from base to side #1 + +<<<<<<< conflict 1 of 1 + +%%%%%%% side #1 compared with base + 1a ++1b - ++++++++ Contents of side #2 - +>>>>>>> Conflict 1 of 1 ends + ++++++++ side #2 + +>>>>>>> conflict 1 of 1 ends diff --git a/file2 b/file2 new file mode 100644 index 0000000000..0000000000 --- /dev/null +++ b/file2 @@ -0,0 +1,7 @@ - +<<<<<<< Conflict 1 of 1 - +%%%%%%% Changes from base to side #1 + +<<<<<<< conflict 1 of 1 + +%%%%%%% side #1 compared with base + 1a +-1b - ++++++++ Contents of side #2 + ++++++++ side #2 +1b - +>>>>>>> Conflict 1 of 1 ends + +>>>>>>> conflict 1 of 1 ends [EOF] "); } diff --git a/cli/tests/test_completion.rs b/cli/tests/test_completion.rs index d4a4bda8c5b..b92fd3c65a5 100644 --- a/cli/tests/test_completion.rs +++ b/cli/tests/test_completion.rs @@ -1356,7 +1356,7 @@ fn test_files() { │ M f_modified │ M f_not_yet_copied │ R {f_not_yet_renamed => f_renamed} - │ × royxmykx test.user@example.com 2001-02-03 08:05:14 conflicted cf10549a conflict + │ × royxmykx test.user@example.com 2001-02-03 08:05:14 conflicted 1bf8de9a conflict ├─╯ conflicted │ A f_added_2 │ A f_dir/dir_file_1 diff --git a/cli/tests/test_diff_command.rs b/cli/tests/test_diff_command.rs index dd0fb28c3a6..2f7deeaf75c 100644 --- a/cli/tests/test_diff_command.rs +++ b/cli/tests/test_diff_command.rs @@ -2461,13 +2461,13 @@ fn test_diff_conflict_sides_differ() { // left1+right1. work_dir.run_jj(["new", "root()"]).success(); insta::assert_snapshot!(work_dir.run_jj(["log", "-r~@"]), @r" - × lylxulpl test.user@example.com 2001-02-03 08:05:20 left2+right2 530ede8c conflict + × lylxulpl test.user@example.com 2001-02-03 08:05:20 left2+right2 7e111ade conflict ├─╮ (empty) left2+right2 │ ○ znkkpsqq test.user@example.com 2001-02-03 08:05:17 right2 e57450eb │ │ right2 ○ │ royxmykx test.user@example.com 2001-02-03 08:05:13 left2 b50b218b │ │ left2 - │ │ × kmkuslsw test.user@example.com 2001-02-03 08:05:18 left1+right1 83cdbdb5 conflict + │ │ × kmkuslsw test.user@example.com 2001-02-03 08:05:18 left1+right1 0ebb5659 conflict ╭─┬─╯ (empty) left1+right1 │ ○ vruxwmqv test.user@example.com 2001-02-03 08:05:15 right1 3fe2e860 │ │ right1 @@ -2530,15 +2530,15 @@ fn test_diff_conflict_sides_differ() { @@ -2,3 +2,11 @@ line 2 -line 3 - +<<<<<<< Conflict 1 of 1 - ++++++++ Contents of side #1 + +<<<<<<< conflict 1 of 1 + ++++++++ zsuskuln 713a980c +left 3.1 +left 3.2 +left 3.3 - +%%%%%%% Changes from base to side #2 + +%%%%%%% vruxwmqv 3fe2e860 compared with rlvkpnrz aa7e33ed +-line 3 ++right 3.1 - +>>>>>>> Conflict 1 of 1 ends + +>>>>>>> conflict 1 of 1 ends line 4 [EOF] "); @@ -2546,15 +2546,15 @@ fn test_diff_conflict_sides_differ() { Created conflict in file:  1  1: line 1  2  2: line 2 -  3: <<<<<<< Conflict 1 of 1 -  4: +++++++ Contents of side #1 +  3: <<<<<<< conflict 1 of 1 +  4: +++++++ zsuskuln 713a980c  5: left 3.1  6: left 3.2  7: left 3.3 -  8: %%%%%%% Changes from base to side #2 +  8: %%%%%%% vruxwmqv 3fe2e860 compared with rlvkpnrz aa7e33ed  3  9: -line 3  10: +right 3.1 -  11: >>>>>>> Conflict 1 of 1 ends +  11: >>>>>>> conflict 1 of 1 ends  4  12: line 4  5  13: line 5 [EOF] @@ -2586,15 +2586,15 @@ fn test_diff_conflict_sides_differ() { +++ b/file @@ -2,11 +2,3 @@ line 2 - -<<<<<<< Conflict 1 of 1 - -+++++++ Contents of side #1 + -<<<<<<< conflict 1 of 1 + -+++++++ zsuskuln 713a980c -left 3.1 -left 3.2 -left 3.3 - -%%%%%%% Changes from base to side #2 + -%%%%%%% vruxwmqv 3fe2e860 compared with rlvkpnrz aa7e33ed --line 3 -+right 3.1 - ->>>>>>> Conflict 1 of 1 ends + ->>>>>>> conflict 1 of 1 ends +line 3 line 4 [EOF] @@ -2603,15 +2603,15 @@ fn test_diff_conflict_sides_differ() { Resolved conflict in file:  1  1: line 1  2  2: line 2 -  3 : <<<<<<< Conflict 1 of 1 -  4 : +++++++ Contents of side #1 +  3 : <<<<<<< conflict 1 of 1 +  4 : +++++++ zsuskuln 713a980c  5 : left 3.1  6 : left 3.2  7 : left 3.3 -  8 : %%%%%%% Changes from base to side #2 +  8 : %%%%%%% vruxwmqv 3fe2e860 compared with rlvkpnrz aa7e33ed  9  3: -line 3  10 : +right 3.1 -  11 : >>>>>>> Conflict 1 of 1 ends +  11 : >>>>>>> conflict 1 of 1 ends  12  4: line 4  13  5: line 5 [EOF] @@ -2640,14 +2640,20 @@ fn test_diff_conflict_sides_differ() { diff --git a/file b/file --- a/file +++ b/file - @@ -1,2 +1,2 @@ + @@ -1,5 +1,5 @@ -line 1 +left 1.1 line 2 - @@ -7,2 +7,3 @@ + <<<<<<< conflict 1 of 1 + -+++++++ zsuskuln 713a980c + ++++++++ royxmykx b50b218b + left 3.1 + @@ -7,3 +7,4 @@ left 3.3 + -%%%%%%% vruxwmqv 3fe2e860 compared with rlvkpnrz aa7e33ed +left 3.4 - %%%%%%% Changes from base to side #2 + +%%%%%%% znkkpsqq e57450eb compared with rlvkpnrz aa7e33ed + -line 3 @@ -12,2 +13,1 @@ line 4 -line 5 @@ -2657,10 +2663,16 @@ fn test_diff_conflict_sides_differ() { Modified conflict in file:  1  1: lineleft 1.1  2  2: line 2 - ... +  3  3: <<<<<<< conflict 1 of 1 +  4 : +++++++ zsuskuln 713a980c +  4: +++++++ royxmykx b50b218b +  5  5: left 3.1 +  6  6: left 3.2  7  7: left 3.3 +  8 : %%%%%%% vruxwmqv 3fe2e860 compared with rlvkpnrz aa7e33ed  8: left 3.4 -  8  9: %%%%%%% Changes from base to side #2 +  9: %%%%%%% znkkpsqq e57450eb compared with rlvkpnrz aa7e33ed +  9  10: -line 3 ...  12  13: line 4  13 : line 5 @@ -2755,7 +2767,7 @@ fn test_diff_conflict_bases_differ() { // left1+right1. work_dir.run_jj(["new", "root()"]).success(); insta::assert_snapshot!(work_dir.run_jj(["log", "-r~@"]), @r" - × nkmrtpmo test.user@example.com 2001-02-03 08:05:22 left2+right2 22cb40d9 conflict + × nkmrtpmo test.user@example.com 2001-02-03 08:05:22 left2+right2 59fe9ac0 conflict ├─╮ (empty) left2+right2 │ ○ kmkuslsw test.user@example.com 2001-02-03 08:05:19 right2 656695c3 │ │ right2 @@ -2763,7 +2775,7 @@ fn test_diff_conflict_bases_differ() { ├─╯ left2 ○ vruxwmqv test.user@example.com 2001-02-03 08:05:15 base2 3c4d67e6 │ base2 - │ × lylxulpl test.user@example.com 2001-02-03 08:05:20 left1+right1 1711cb65 conflict + │ × lylxulpl test.user@example.com 2001-02-03 08:05:20 left1+right1 abb83634 conflict │ ├─╮ (empty) left1+right1 │ │ ○ royxmykx test.user@example.com 2001-02-03 08:05:13 right1 3087be1f ├───╯ right1 @@ -2809,12 +2821,18 @@ fn test_diff_conflict_bases_differ() { diff --git a/file b/file --- a/file +++ b/file - @@ -1,2 +1,1 @@ + @@ -1,5 +1,4 @@ -line 1 line 2 - @@ -8,3 +7,4 @@ - %%%%%%% Changes from base to side #2 + <<<<<<< conflict 1 of 1 + -+++++++ zsuskuln 9e995075 + ++++++++ znkkpsqq 218094ec + left 3.1 + @@ -7,4 +6,5 @@ + left 3.3 + -%%%%%%% royxmykx 3087be1f compared with rlvkpnrz 44cfbde6 --line 3 + +%%%%%%% kmkuslsw 656695c3 compared with vruxwmqv 3c4d67e6 +-line 3.1 +-line 3.2 +right 3.1 @@ -2824,10 +2842,17 @@ fn test_diff_conflict_bases_differ() { Modified conflict in file:  1 : line 1  2  1: line 2 - ... -  8  7: %%%%%%% Changes from base to side #2 -  9  8: -line 3.1 -  9  9: -line 3.2 +  3  2: <<<<<<< conflict 1 of 1 +  4 : +++++++ zsuskuln 9e995075 +  3: +++++++ znkkpsqq 218094ec +  5  4: left 3.1 +  6  5: left 3.2 +  7  6: left 3.3 +  8 : %%%%%%% royxmykx 3087be1f compared with rlvkpnrz 44cfbde6 +  9 : -line 3 +  7: %%%%%%% kmkuslsw 656695c3 compared with vruxwmqv 3c4d67e6 +  8: -line 3.1 +  9: -line 3.2  10  10: +right 3.1 ... [EOF] @@ -2911,11 +2936,11 @@ fn test_diff_conflict_three_sides() { // Test the setup work_dir.run_jj(["new", "root()"]).success(); insta::assert_snapshot!(work_dir.run_jj(["log", "-r~@"]), @r" - × lylxulpl test.user@example.com 2001-02-03 08:05:20 side1+side2+side3 ac1efd43 conflict + × lylxulpl test.user@example.com 2001-02-03 08:05:20 side1+side2+side3 dc9c9180 conflict ├─╮ (empty) side1+side2+side3 │ ○ znkkpsqq test.user@example.com 2001-02-03 08:05:17 side3 f73063c9 │ │ side3 - × │ kmkuslsw test.user@example.com 2001-02-03 08:05:18 side1+side2 a1f24e10 conflict + × │ kmkuslsw test.user@example.com 2001-02-03 08:05:18 side1+side2 2636a1ea conflict ├───╮ (empty) side1+side2 │ │ ○ vruxwmqv test.user@example.com 2001-02-03 08:05:15 side2 bc176227 │ │ │ side2 @@ -2971,31 +2996,22 @@ fn test_diff_conflict_three_sides() { diff --git a/file b/file --- a/file +++ b/file - @@ -2,3 +2,3 @@ - <<<<<<< Conflict 1 of 1 - -%%%%%%% Changes from base to side #1 - +%%%%%%% Changes from base #1 to side #1 - -line 2 base @@ -12,2 +12,5 @@ line 4 b.2 - +%%%%%%% Changes from base #2 to side #3 + +%%%%%%% znkkpsqq f73063c9 compared with rlvkpnrz 07965fa1 + line 2 base ++line 3 c.2 - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends [EOF] "); insta::assert_snapshot!(diff_color_words_materialized("side1+side2", "side1+side2+side3"), @r" Modified conflict in file: -  1  1: line 1 -  2  2: <<<<<<< Conflict 1 of 1 -  3  3: %%%%%%% Changes from base #1 to side #1 -  4  4: -line 2 base ...  12  12: line 4 b.2 -  13: %%%%%%% Changes from base #2 to side #3 +  13: %%%%%%% znkkpsqq f73063c9 compared with rlvkpnrz 07965fa1  14:  line 2 base  15: +line 3 c.2 -  13  16: >>>>>>> Conflict 1 of 1 ends +  13  16: >>>>>>> conflict 1 of 1 ends  14  17: line 5 [EOF] "); @@ -3556,15 +3572,15 @@ fn test_diff_external_tool_conflict_marker_style() { line 2.2 line 2.3 line 3 - <<<<<<< Conflict 1 of 1 - +++++++ Contents of side #1 + <<<<<<< conflict 1 of 1 + +++++++ rlvkpnrz 74e448a1 line 4.1 - ------- Contents of base + ------- qpvuntsm 9bd2e004 line 4 - +++++++ Contents of side #2 + +++++++ zsuskuln 6982bce7 line 4.2 line 4.3 - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends line 5 "); } diff --git a/cli/tests/test_diffedit_command.rs b/cli/tests/test_diffedit_command.rs index 02d1fd6766b..ac2dcb15532 100644 --- a/cli/tests/test_diffedit_command.rs +++ b/cli/tests/test_diffedit_command.rs @@ -392,7 +392,7 @@ fn test_diffedit_external_tool_conflict_marker_style() { let output = work_dir.run_jj(["diffedit"]); insta::assert_snapshot!(output, @r" ------- stderr ------- - Working copy (@) now at: mzvwutvl 268f208f (conflict) (empty) (no description set) + Working copy (@) now at: mzvwutvl 8b726390 (conflict) (empty) (no description set) Parent commit (@-) : rlvkpnrz 74e448a1 side-a Parent commit (@-) : zsuskuln 6982bce7 side-b Added 0 files, modified 1 files, removed 0 files @@ -405,25 +405,25 @@ fn test_diffedit_external_tool_conflict_marker_style() { insta::assert_snapshot!( std::fs::read_to_string(test_env.env_root().join("before-file")).unwrap(), @r" line 1 - <<<<<<< Conflict 1 of 2 - +++++++ Contents of side #1 + <<<<<<< conflict 1 of 2 + +++++++ rlvkpnrz 74e448a1 line 2.1 line 2.2 - ------- Contents of base + ------- qpvuntsm 9bd2e004 line 2 - +++++++ Contents of side #2 + +++++++ zsuskuln 6982bce7 line 2.3 - >>>>>>> Conflict 1 of 2 ends + >>>>>>> conflict 1 of 2 ends line 3 - <<<<<<< Conflict 2 of 2 - +++++++ Contents of side #1 + <<<<<<< conflict 2 of 2 + +++++++ rlvkpnrz 74e448a1 line 4.1 - ------- Contents of base + ------- qpvuntsm 9bd2e004 line 4 - +++++++ Contents of side #2 + +++++++ zsuskuln 6982bce7 line 4.2 line 4.3 - >>>>>>> Conflict 2 of 2 ends + >>>>>>> conflict 2 of 2 ends line 5 "); insta::assert_snapshot!( @@ -433,37 +433,37 @@ fn test_diffedit_external_tool_conflict_marker_style() { line 2.2 line 2.3 line 3 - <<<<<<< Conflict 1 of 1 - +++++++ Contents of side #1 + <<<<<<< conflict 1 of 1 + +++++++ rlvkpnrz 74e448a1 line 4.1 - ------- Contents of base + ------- qpvuntsm 9bd2e004 line 4 - +++++++ Contents of side #2 + +++++++ zsuskuln 6982bce7 line 4.2 line 4.3 - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends line 5 "); // Conflicts should be materialized using "diff" format in working copy insta::assert_snapshot!(work_dir.read_file(file_path), @r" line 1 - <<<<<<< Conflict 1 of 2 - +++++++ Contents of side #1 + <<<<<<< conflict 1 of 2 + +++++++ rlvkpnrz 74e448a1 line 2.1 line 2.2 - %%%%%%% Changes from base to side #2 + %%%%%%% zsuskuln 6982bce7 compared with qpvuntsm 9bd2e004 -line 2 +line 2.3 - >>>>>>> Conflict 1 of 2 ends + >>>>>>> conflict 1 of 2 ends line 3 - <<<<<<< Conflict 2 of 2 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 2 of 2 + %%%%%%% rlvkpnrz 74e448a1 compared with qpvuntsm 9bd2e004 -line 4 +line 4.1 - +++++++ Contents of side #2 + +++++++ zsuskuln 6982bce7 line 4.2 line 4.3 - >>>>>>> Conflict 2 of 2 ends + >>>>>>> conflict 2 of 2 ends line 5 "); @@ -471,7 +471,7 @@ fn test_diffedit_external_tool_conflict_marker_style() { let output = work_dir.run_jj(["st"]); insta::assert_snapshot!(output, @r" The working copy has no changes. - Working copy (@) : mzvwutvl 268f208f (conflict) (empty) (no description set) + Working copy (@) : mzvwutvl 8b726390 (conflict) (empty) (no description set) Parent commit (@-): rlvkpnrz 74e448a1 side-a Parent commit (@-): zsuskuln 6982bce7 side-b Warning: There are unresolved conflicts at these paths: @@ -633,8 +633,8 @@ fn test_diffedit_merge() { insta::assert_snapshot!(output, @r" ------- stderr ------- Rebased 1 descendant commits - Working copy (@) now at: yqosqzyt ce686d54 (conflict) (empty) (no description set) - Parent commit (@-) : royxmykx 47cae64e (conflict) merge + Working copy (@) now at: yqosqzyt 1a031ee4 (conflict) (empty) (no description set) + Parent commit (@-) : royxmykx 17b63d75 (conflict) merge Added 0 files, modified 0 files, removed 1 files Warning: There are unresolved conflicts at these paths: file2 2-sided conflict @@ -649,13 +649,13 @@ fn test_diffedit_merge() { assert!(!work_dir.root().join("file1").exists()); let output = work_dir.run_jj(["file", "show", "file2"]); insta::assert_snapshot!(output, @r" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% mzvwutvl e3b18dc7 compared with qpvuntsm fc6f5e82 -a +c - +++++++ Contents of side #2 + +++++++ rlvkpnrz 7027fb26 b - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends [EOF] "); } diff --git a/cli/tests/test_evolog_command.rs b/cli/tests/test_evolog_command.rs index bf5d0b91a1d..9d50535cb55 100644 --- a/cli/tests/test_evolog_command.rs +++ b/cli/tests/test_evolog_command.rs @@ -34,10 +34,10 @@ fn test_evolog_with_or_without_diff() { insta::assert_snapshot!(output, @r" @ rlvkpnrz test.user@example.com 2001-02-03 08:05:10 33c10ace │ my description - │ -- operation 3499115d3831 snapshot working copy - × rlvkpnrz hidden test.user@example.com 2001-02-03 08:05:09 7f56b2a0 conflict + │ -- operation 6ddc92b5d86e snapshot working copy + × rlvkpnrz hidden test.user@example.com 2001-02-03 08:05:09 05d7186b conflict │ my description - │ -- operation eb87ec366530 rebase commit 51e08f95160c897080d035d330aead3ee6ed5588 + │ -- operation 7981cfb96904 rebase commit 51e08f95160c897080d035d330aead3ee6ed5588 ○ rlvkpnrz hidden test.user@example.com 2001-02-03 08:05:09 51e08f95 │ my description │ -- operation 18a971ce330a snapshot working copy @@ -52,10 +52,10 @@ fn test_evolog_with_or_without_diff() { insta::assert_snapshot!(output, @r" @ rlvkpnrz test.user@example.com 2001-02-03 08:05:10 33c10ace │ my description - │ -- operation 3499115d3831 snapshot working copy - × rlvkpnrz hidden test.user@example.com 2001-02-03 08:05:09 7f56b2a0 conflict + │ -- operation 6ddc92b5d86e snapshot working copy + × rlvkpnrz hidden test.user@example.com 2001-02-03 08:05:09 05d7186b conflict │ my description - │ -- operation eb87ec366530 rebase commit 51e08f95160c897080d035d330aead3ee6ed5588 + │ -- operation 7981cfb96904 rebase commit 51e08f95160c897080d035d330aead3ee6ed5588 ○ rlvkpnrz hidden test.user@example.com 2001-02-03 08:05:09 51e08f95 │ my description │ -- operation 18a971ce330a snapshot working copy @@ -71,18 +71,18 @@ fn test_evolog_with_or_without_diff() { insta::assert_snapshot!(output, @r" @ rlvkpnrz test.user@example.com 2001-02-03 08:05:10 33c10ace │ my description - │ -- operation 3499115d3831 snapshot working copy + │ -- operation 6ddc92b5d86e snapshot working copy │ Resolved conflict in file1: - │ 1 : <<<<<<< Conflict 1 of 1 - │ 2 : %%%%%%% Changes from base to side #1 + │ 1 : <<<<<<< conflict 1 of 1 + │ 2 : %%%%%%% rebase destination (zzzzzzzz 00000000) compared with parents of rlvkpnrz 51e08f95 │ 3 : -foo - │ 4 : +++++++ Contents of side #2 + │ 4 : +++++++ rebased commit (rlvkpnrz 51e08f95) │ 5 : foo │ 6 : bar - │ 7 1: >>>>>>> Conflict 1 of 1 endsresolved - × rlvkpnrz hidden test.user@example.com 2001-02-03 08:05:09 7f56b2a0 conflict + │ 7 1: >>>>>>> conflict 1 of 1 endsresolved + × rlvkpnrz hidden test.user@example.com 2001-02-03 08:05:09 05d7186b conflict │ my description - │ -- operation eb87ec366530 rebase commit 51e08f95160c897080d035d330aead3ee6ed5588 + │ -- operation 7981cfb96904 rebase commit 51e08f95160c897080d035d330aead3ee6ed5588 ○ rlvkpnrz hidden test.user@example.com 2001-02-03 08:05:09 51e08f95 │ my description │ -- operation 18a971ce330a snapshot working copy @@ -104,10 +104,10 @@ fn test_evolog_with_or_without_diff() { insta::assert_snapshot!(output, @r" @ rlvkpnrz test.user@example.com 2001-02-03 08:05:10 33c10ace │ my description - │ -- operation 3499115d3831 snapshot working copy - × rlvkpnrz hidden test.user@example.com 2001-02-03 08:05:09 7f56b2a0 conflict + │ -- operation 6ddc92b5d86e snapshot working copy + × rlvkpnrz hidden test.user@example.com 2001-02-03 08:05:09 05d7186b conflict │ my description - │ -- operation eb87ec366530 rebase commit 51e08f95160c897080d035d330aead3ee6ed5588 + │ -- operation 7981cfb96904 rebase commit 51e08f95160c897080d035d330aead3ee6ed5588 ○ rlvkpnrz hidden test.user@example.com 2001-02-03 08:05:09 51e08f95 │ my description │ -- operation 18a971ce330a snapshot working copy @@ -128,10 +128,10 @@ fn test_evolog_with_or_without_diff() { insta::assert_snapshot!(output, @r" @ rlvkpnrz test.user@example.com 2001-02-03 08:05:10 33c10ace │ my description - │ -- operation 3499115d3831 snapshot working copy - × rlvkpnrz hidden test.user@example.com 2001-02-03 08:05:09 7f56b2a0 conflict + │ -- operation 6ddc92b5d86e snapshot working copy + × rlvkpnrz hidden test.user@example.com 2001-02-03 08:05:09 05d7186b conflict │ my description - │ -- operation eb87ec366530 rebase commit 51e08f95160c897080d035d330aead3ee6ed5588 + │ -- operation 7981cfb96904 rebase commit 51e08f95160c897080d035d330aead3ee6ed5588 [EOF] "); @@ -140,10 +140,10 @@ fn test_evolog_with_or_without_diff() { insta::assert_snapshot!(output, @r" rlvkpnrz test.user@example.com 2001-02-03 08:05:10 33c10ace my description - -- operation 3499115d3831 snapshot working copy - rlvkpnrz hidden test.user@example.com 2001-02-03 08:05:09 7f56b2a0 conflict + -- operation 6ddc92b5d86e snapshot working copy + rlvkpnrz hidden test.user@example.com 2001-02-03 08:05:09 05d7186b conflict my description - -- operation eb87ec366530 rebase commit 51e08f95160c897080d035d330aead3ee6ed5588 + -- operation 7981cfb96904 rebase commit 51e08f95160c897080d035d330aead3ee6ed5588 rlvkpnrz hidden test.user@example.com 2001-02-03 08:05:09 51e08f95 my description -- operation 18a971ce330a snapshot working copy @@ -158,23 +158,23 @@ fn test_evolog_with_or_without_diff() { insta::assert_snapshot!(output, @r" rlvkpnrz test.user@example.com 2001-02-03 08:05:10 33c10ace my description - -- operation 3499115d3831 snapshot working copy + -- operation 6ddc92b5d86e snapshot working copy diff --git a/file1 b/file1 index 0000000000..2ab19ae607 100644 --- a/file1 +++ b/file1 @@ -1,7 +1,1 @@ - -<<<<<<< Conflict 1 of 1 - -%%%%%%% Changes from base to side #1 + -<<<<<<< conflict 1 of 1 + -%%%%%%% rebase destination (zzzzzzzz 00000000) compared with parents of rlvkpnrz 51e08f95 --foo - -+++++++ Contents of side #2 + -+++++++ rebased commit (rlvkpnrz 51e08f95) -foo -bar - ->>>>>>> Conflict 1 of 1 ends + ->>>>>>> conflict 1 of 1 ends +resolved - rlvkpnrz hidden test.user@example.com 2001-02-03 08:05:09 7f56b2a0 conflict + rlvkpnrz hidden test.user@example.com 2001-02-03 08:05:09 05d7186b conflict my description - -- operation eb87ec366530 rebase commit 51e08f95160c897080d035d330aead3ee6ed5588 + -- operation 7981cfb96904 rebase commit 51e08f95160c897080d035d330aead3ee6ed5588 rlvkpnrz hidden test.user@example.com 2001-02-03 08:05:09 51e08f95 my description -- operation 18a971ce330a snapshot working copy @@ -293,10 +293,10 @@ fn test_evolog_with_custom_symbols() { insta::assert_snapshot!(output, @r" $ rlvkpnrz test.user@example.com 2001-02-03 08:05:10 33c10ace │ my description - │ -- operation 3622beb20303 snapshot working copy - ┝ rlvkpnrz hidden test.user@example.com 2001-02-03 08:05:09 7f56b2a0 conflict + │ -- operation cafb10864ef4 snapshot working copy + ┝ rlvkpnrz hidden test.user@example.com 2001-02-03 08:05:09 05d7186b conflict │ my description - │ -- operation eb87ec366530 rebase commit 51e08f95160c897080d035d330aead3ee6ed5588 + │ -- operation 7981cfb96904 rebase commit 51e08f95160c897080d035d330aead3ee6ed5588 ┝ rlvkpnrz hidden test.user@example.com 2001-02-03 08:05:09 51e08f95 │ my description │ -- operation 18a971ce330a snapshot working copy @@ -418,14 +418,14 @@ fn test_evolog_squash() { ├─┬─╮ squashed 3 │ │ │ -- operation 838e6d867fda squash commits into 5ec0619af5cb4f7707a556a71a6f96af0bc294d2 │ │ │ Modified commit description: - │ │ │ 1 : <<<<<<< Conflict 1 of 1 - │ │ │ 2 : +++++++ Contents of side #1 + │ │ │ 1 : <<<<<<< conflict 1 of 1 + │ │ │ 2 : +++++++ side #1 │ │ │ 3 1: squashed 2 - │ │ │ 4 : %%%%%%% Changes from base #1 to side #2 + │ │ │ 4 : %%%%%%% side #2 compared with base #1 │ │ │ 5 : +fourth - │ │ │ 6 1: %%%%%%% Changes from base #2 to side #3 + │ │ │ 6 1: %%%%%%% side #3 compared with base #2 │ │ │ 7 : +fifth - │ │ │ 8 : >>>>>>> Conflict 1 of 1 ends + │ │ │ 8 1: >>>>>>> conflict 1 of 1 ends │ │ ○ vruxwmqv hidden test.user@example.com 2001-02-03 08:05:15 770795d0 │ │ │ fifth │ │ │ -- operation 1d38c000b52d snapshot working copy @@ -450,12 +450,12 @@ fn test_evolog_squash() { ├─╮ squashed 2 │ │ -- operation fa9796d12627 squash commits into 690858846504af0e42fde980fdacf9851559ebb8 │ │ Modified commit description: - │ │ 1 : <<<<<<< Conflict 1 of 1 - │ │ 2 : +++++++ Contents of side #1 + │ │ 1 : <<<<<<< conflict 1 of 1 + │ │ 2 : +++++++ side #1 │ │ 3 1: squashed 1 - │ │ 4 1: %%%%%%% Changes from base to side #2 + │ │ 4 1: %%%%%%% side #2 compared with base │ │ 5 : +third - │ │ 6 : >>>>>>> Conflict 1 of 1 ends + │ │ 6 1: >>>>>>> conflict 1 of 1 ends │ │ Removed regular file file2: │ │ 1 : foo2 │ │ Removed regular file file3: @@ -483,12 +483,12 @@ fn test_evolog_squash() { ├─╮ squashed 1 │ │ -- operation 65c81703100d squash commits into 5878cbe03cdf599c9353e5a1a52a01f4c5e0e0fa │ │ Modified commit description: - │ │ 1 : <<<<<<< Conflict 1 of 1 - │ │ 2 : %%%%%%% Changes from base to side #1 + │ │ 1 : <<<<<<< conflict 1 of 1 + │ │ 2 : %%%%%%% side #1 compared with base │ │ 3 : +first - │ │ 4 : +++++++ Contents of side #2 + │ │ 4 : +++++++ side #2 │ │ 5 : second - │ │ 6 : >>>>>>> Conflict 1 of 1 ends + │ │ 6 : >>>>>>> conflict 1 of 1 ends │ │ 1: squashed 1 │ ○ kkmpptxz hidden test.user@example.com 2001-02-03 08:05:10 a3759c9d │ │ second diff --git a/cli/tests/test_file_annotate_command.rs b/cli/tests/test_file_annotate_command.rs index a5cd799f501..ea25e895030 100644 --- a/cli/tests/test_file_annotate_command.rs +++ b/cli/tests/test_file_annotate_command.rs @@ -138,12 +138,12 @@ fn test_annotate_conflicted() { let output = work_dir.run_jj(["file", "annotate", "file.txt"]); insta::assert_snapshot!(output, @r" qpvuntsm test.use 2001-02-03 08:05:08 1: line1 - yostqsxw test.use 2001-02-03 08:05:15 2: <<<<<<< Conflict 1 of 1 - yostqsxw test.use 2001-02-03 08:05:15 3: %%%%%%% Changes from base to side #1 + yostqsxw test.use 2001-02-03 08:05:15 2: <<<<<<< conflict 1 of 1 + yostqsxw test.use 2001-02-03 08:05:15 3: %%%%%%% zsuskuln 30cd4478 compared with qpvuntsm a5daff01 yostqsxw test.use 2001-02-03 08:05:15 4: +new text from new commit 1 - yostqsxw test.use 2001-02-03 08:05:15 5: +++++++ Contents of side #2 + yostqsxw test.use 2001-02-03 08:05:15 5: +++++++ royxmykx ad312256 royxmykx test.use 2001-02-03 08:05:13 6: new text from new commit 2 - yostqsxw test.use 2001-02-03 08:05:15 7: >>>>>>> Conflict 1 of 1 ends + yostqsxw test.use 2001-02-03 08:05:15 7: >>>>>>> conflict 1 of 1 ends [EOF] "); } diff --git a/cli/tests/test_file_chmod_command.rs b/cli/tests/test_file_chmod_command.rs index 6861efdb63b..e3383134aef 100644 --- a/cli/tests/test_file_chmod_command.rs +++ b/cli/tests/test_file_chmod_command.rs @@ -55,13 +55,13 @@ fn test_chmod_regular_conflict() { "#); let output = work_dir.run_jj(["file", "show", "file"]); insta::assert_snapshot!(output, @r" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% royxmykx 02247291 compared with rlvkpnrz 1792382a -base +x - +++++++ Contents of side #2 + +++++++ zsuskuln eb0ba805 n - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends [EOF] "); @@ -74,13 +74,13 @@ fn test_chmod_regular_conflict() { "#); let output = work_dir.run_jj(["file", "show", "file"]); insta::assert_snapshot!(output, @r" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% royxmykx 02247291 compared with rlvkpnrz 1792382a -base +x - +++++++ Contents of side #2 + +++++++ zsuskuln eb0ba805 n - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends [EOF] "); work_dir.run_jj(["file", "chmod", "n", "file"]).success(); @@ -91,13 +91,13 @@ fn test_chmod_regular_conflict() { "#); let output = work_dir.run_jj(["file", "show", "file"]); insta::assert_snapshot!(output, @r" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% royxmykx 02247291 compared with rlvkpnrz 1792382a -base +x - +++++++ Contents of side #2 + +++++++ zsuskuln eb0ba805 n - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends [EOF] "); @@ -106,7 +106,7 @@ fn test_chmod_regular_conflict() { insta::assert_snapshot!(output, @r" ------- stderr ------- Warning: No matching entries for paths: nonexistent - Working copy (@) now at: yostqsxw df2619be conflict | (conflict) conflict + Working copy (@) now at: yostqsxw a573cc0f conflict | (conflict) conflict Parent commit (@-) : royxmykx 02247291 x | x Parent commit (@-) : zsuskuln eb0ba805 n | n Added 0 files, modified 1 files, removed 0 files @@ -185,25 +185,25 @@ fn test_chmod_file_dir_deletion_conflicts() { "#); let output = work_dir.run_jj(["file", "show", "-r=file_deletion", "file"]); insta::assert_snapshot!(output, @r" - <<<<<<< Conflict 1 of 1 - +++++++ Contents of side #1 + <<<<<<< conflict 1 of 1 + +++++++ zsuskuln bc9cdea1 a - %%%%%%% Changes from base to side #2 + %%%%%%% royxmykx d7d39332 compared with rlvkpnrz 1792382a -base - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends [EOF] "); let output = work_dir.run_jj(["file", "chmod", "x", "file", "-r=file_deletion"]); - insta::assert_snapshot!(output, @r###" + insta::assert_snapshot!(output, @r" ------- stderr ------- - Working copy (@) now at: kmkuslsw dc89f9e7 file_deletion | (conflict) file_deletion + Working copy (@) now at: kmkuslsw 3e483d61 file_deletion | (conflict) file_deletion Parent commit (@-) : zsuskuln bc9cdea1 file | file Parent commit (@-) : royxmykx d7d39332 deletion | deletion Added 0 files, modified 1 files, removed 0 files Warning: There are unresolved conflicts at these paths: file 2-sided conflict including 1 deletion and an executable New conflicts appeared in 1 commits: - kmkuslsw dc89f9e7 file_deletion | (conflict) file_deletion + kmkuslsw 3e483d61 file_deletion | (conflict) file_deletion Hint: To resolve the conflicts, start by creating a commit on top of the conflicted commit: jj new kmkuslsw @@ -211,7 +211,7 @@ fn test_chmod_file_dir_deletion_conflicts() { Once the conflicts are resolved, you can inspect the result with `jj diff`. Then run `jj squash` to move the resolution into the conflicted commit. [EOF] - "###); + "); let output = work_dir.run_jj(["debug", "tree", "-r=file_deletion"]); insta::assert_snapshot!(output, @r#" file: Ok(Conflicted([Some(File { id: FileId("78981922613b2afb6025042ff6bd878ac1994e85"), executable: true, copy_id: CopyId("") }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: true, copy_id: CopyId("") }), None])) @@ -219,12 +219,12 @@ fn test_chmod_file_dir_deletion_conflicts() { "#); let output = work_dir.run_jj(["file", "show", "-r=file_deletion", "file"]); insta::assert_snapshot!(output, @r" - <<<<<<< Conflict 1 of 1 - +++++++ Contents of side #1 + <<<<<<< conflict 1 of 1 + +++++++ zsuskuln bc9cdea1 a - %%%%%%% Changes from base to side #2 + %%%%%%% royxmykx d7d39332 compared with rlvkpnrz 1792382a -base - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends [EOF] "); } diff --git a/cli/tests/test_file_show_command.rs b/cli/tests/test_file_show_command.rs index 8659ed0769e..5daf4e85847 100644 --- a/cli/tests/test_file_show_command.rs +++ b/cli/tests/test_file_show_command.rs @@ -105,13 +105,13 @@ fn test_show() { .success(); let output = work_dir.run_jj(["file", "show", "file1"]); insta::assert_snapshot!(output, @r" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% rebase destination (qpvuntsm eb7b8a1f) compared with parents of kpqxywon 9433f7fb -b +a - +++++++ Contents of side #2 + +++++++ rebased commit (kpqxywon 9433f7fb) c - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends [EOF] "); } diff --git a/cli/tests/test_fix_command.rs b/cli/tests/test_fix_command.rs index 5fcecfa0c5f..e62575fd560 100644 --- a/cli/tests/test_fix_command.rs +++ b/cli/tests/test_fix_command.rs @@ -1337,7 +1337,7 @@ fn test_fix_both_sides_of_conflict() { insta::assert_snapshot!(output, @r" ------- stderr ------- Fixed 3 commits of 3 checked. - Working copy (@) now at: mzvwutvl d4d02bf0 (conflict) (empty) (no description set) + Working copy (@) now at: mzvwutvl 47e7aa32 (conflict) (empty) (no description set) Parent commit (@-) : qpvuntsm 0eae0dae a | (no description set) Parent commit (@-) : kkmpptxz eb61ba8d b | (no description set) Added 0 files, modified 1 files, removed 0 files @@ -1357,12 +1357,12 @@ fn test_fix_both_sides_of_conflict() { "); let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]); insta::assert_snapshot!(output, @r" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% qpvuntsm c8346c1c compared with zzzzzzzz 00000000 +CONTENT A - +++++++ Contents of side #2 + +++++++ kkmpptxz 7c4518cb CONTENT B - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends [EOF] "); } diff --git a/cli/tests/test_git_colocated.rs b/cli/tests/test_git_colocated.rs index 0664dd08841..b088c481cc5 100644 --- a/cli/tests/test_git_colocated.rs +++ b/cli/tests/test_git_colocated.rs @@ -1138,7 +1138,7 @@ fn test_git_colocated_update_index_merge_conflict() { work_dir.run_jj(["new", "left", "right"]).success(); insta::assert_snapshot!(get_log_output(&work_dir), @r" - @ 8b05232ad2cda6f6d06b290486e07251f53c0958 + @ 32575c53fa994da7b6413462b3a84a1a8bd2755d ├─╮ │ ○ 620e15db9fcd05fff912c52d2cafd36c9e01523c right ○ │ d0f55ffafa1e0e72980202c349af23d093f825be left git_head() @@ -1162,8 +1162,8 @@ fn test_git_colocated_update_index_merge_conflict() { work_dir.run_jj(["new"]).success(); insta::assert_snapshot!(get_log_output(&work_dir), @r" - @ 7c98aa1e17acd7829c9ccb9eaae705df9b255bd1 - × 8b05232ad2cda6f6d06b290486e07251f53c0958 git_head() + @ 1a9448c39957ac0fa19f6d7a490748a72a655353 + × 32575c53fa994da7b6413462b3a84a1a8bd2755d git_head() ├─╮ │ ○ 620e15db9fcd05fff912c52d2cafd36c9e01523c right ○ │ d0f55ffafa1e0e72980202c349af23d093f825be left @@ -1245,7 +1245,7 @@ fn test_git_colocated_update_index_rebase_conflict() { .success(); insta::assert_snapshot!(get_log_output(&work_dir), @r" - @ 535388c5aab1b3a33fdc04a4bf8033de0d1b86ec left + @ dcceaec5f93fbe640cdc45a880e2be7a5f38584b left ○ 620e15db9fcd05fff912c52d2cafd36c9e01523c right git_head() ○ 1861378a9167e6561bf8ce4a6fef2d7c0897dd87 base ◆ 0000000000000000000000000000000000000000 @@ -1264,8 +1264,8 @@ fn test_git_colocated_update_index_rebase_conflict() { work_dir.run_jj(["new"]).success(); insta::assert_snapshot!(get_log_output(&work_dir), @r" - @ 04ebd7523ac6107ccdd5bc34600a073b94e43299 - × 535388c5aab1b3a33fdc04a4bf8033de0d1b86ec left git_head() + @ 67b687cd95c53b410c2291a008b24f03c3ad3e70 + × dcceaec5f93fbe640cdc45a880e2be7a5f38584b left git_head() ○ 620e15db9fcd05fff912c52d2cafd36c9e01523c right ○ 1861378a9167e6561bf8ce4a6fef2d7c0897dd87 base ◆ 0000000000000000000000000000000000000000 @@ -1341,7 +1341,7 @@ fn test_git_colocated_update_index_3_sided_conflict() { .success(); insta::assert_snapshot!(get_log_output(&work_dir), @r" - @ 3105daa0d68e3cdc22b2533d7d1b231cd41c76ec + @ cc991bcfa52210c8aeef5cf8e4e65b84a90f3367 ├─┬─╮ │ │ ○ 5008c8807feaa955d02e96cb1b0dcf51536fefb8 side-3 │ ○ │ da6e0a03f8b72f6868a9ea33836123fe965c0cb4 side-2 @@ -1367,8 +1367,8 @@ fn test_git_colocated_update_index_3_sided_conflict() { work_dir.run_jj(["new"]).success(); insta::assert_snapshot!(get_log_output(&work_dir), @r" - @ 5b4266a02e8fe9febc6294c7d0a02fc8463221e8 - × 3105daa0d68e3cdc22b2533d7d1b231cd41c76ec git_head() + @ 5fdc5b3e23997a574cd31a9c272f61b3d090cebe + × cc991bcfa52210c8aeef5cf8e4e65b84a90f3367 git_head() ├─┬─╮ │ │ ○ 5008c8807feaa955d02e96cb1b0dcf51536fefb8 side-3 │ ○ │ da6e0a03f8b72f6868a9ea33836123fe965c0cb4 side-2 diff --git a/cli/tests/test_git_push.rs b/cli/tests/test_git_push.rs index 67ed4c2d813..fe1cdd78481 100644 --- a/cli/tests/test_git_push.rs +++ b/cli/tests/test_git_push.rs @@ -1424,8 +1424,8 @@ fn test_git_push_conflict() { let output = work_dir.run_jj(["git", "push", "--all"]); insta::assert_snapshot!(output, @r" ------- stderr ------- - Error: Won't push commit 654e715becca since it has conflicts - Hint: Rejected commit: yostqsxw 654e715b my-bookmark | (conflict) third + Error: Won't push commit d24c46f9d615 since it has conflicts + Hint: Rejected commit: yostqsxw d24c46f9 my-bookmark | (conflict) third [EOF] [exit status: 1] "); diff --git a/cli/tests/test_immutable_commits.rs b/cli/tests/test_immutable_commits.rs index 4dada6f9c2b..fdd4ba6e69b 100644 --- a/cli/tests/test_immutable_commits.rs +++ b/cli/tests/test_immutable_commits.rs @@ -247,7 +247,7 @@ fn test_rewrite_immutable_commands() { insta::assert_snapshot!(output, @r" @ yqosqzyt test.user@example.com 2001-02-03 08:05:14 55c97dc7 │ (no description set) - │ ◆ mzvwutvl test.user@example.com 2001-02-03 08:05:12 main 4397373a conflict + │ ◆ mzvwutvl test.user@example.com 2001-02-03 08:05:12 main 0ff570e4 conflict ╭─┤ merge │ │ │ ~ @@ -262,8 +262,8 @@ fn test_rewrite_immutable_commands() { let output = work_dir.run_jj(["abandon", "main"]); insta::assert_snapshot!(output, @r#" ------- stderr ------- - Error: Commit 4397373a0991 is immutable - Hint: Could not modify commit: mzvwutvl 4397373a main | (conflict) merge + Error: Commit 0ff570e4c05e is immutable + Hint: Could not modify commit: mzvwutvl 0ff570e4 main | (conflict) merge Hint: Immutable commits are used to protect shared history. Hint: For more information, see: - https://jj-vcs.github.io/jj/latest/config/#set-of-immutable-commits @@ -290,8 +290,8 @@ fn test_rewrite_immutable_commands() { let output = work_dir.run_jj(["file", "chmod", "-r=main", "x", "file"]); insta::assert_snapshot!(output, @r#" ------- stderr ------- - Error: Commit 4397373a0991 is immutable - Hint: Could not modify commit: mzvwutvl 4397373a main | (conflict) merge + Error: Commit 0ff570e4c05e is immutable + Hint: Could not modify commit: mzvwutvl 0ff570e4 main | (conflict) merge Hint: Immutable commits are used to protect shared history. Hint: For more information, see: - https://jj-vcs.github.io/jj/latest/config/#set-of-immutable-commits @@ -304,8 +304,8 @@ fn test_rewrite_immutable_commands() { let output = work_dir.run_jj(["describe", "main"]); insta::assert_snapshot!(output, @r#" ------- stderr ------- - Error: Commit 4397373a0991 is immutable - Hint: Could not modify commit: mzvwutvl 4397373a main | (conflict) merge + Error: Commit 0ff570e4c05e is immutable + Hint: Could not modify commit: mzvwutvl 0ff570e4 main | (conflict) merge Hint: Immutable commits are used to protect shared history. Hint: For more information, see: - https://jj-vcs.github.io/jj/latest/config/#set-of-immutable-commits @@ -318,8 +318,8 @@ fn test_rewrite_immutable_commands() { let output = work_dir.run_jj(["diffedit", "-r=main"]); insta::assert_snapshot!(output, @r#" ------- stderr ------- - Error: Commit 4397373a0991 is immutable - Hint: Could not modify commit: mzvwutvl 4397373a main | (conflict) merge + Error: Commit 0ff570e4c05e is immutable + Hint: Could not modify commit: mzvwutvl 0ff570e4 main | (conflict) merge Hint: Immutable commits are used to protect shared history. Hint: For more information, see: - https://jj-vcs.github.io/jj/latest/config/#set-of-immutable-commits @@ -332,8 +332,8 @@ fn test_rewrite_immutable_commands() { let output = work_dir.run_jj(["edit", "main"]); insta::assert_snapshot!(output, @r#" ------- stderr ------- - Error: Commit 4397373a0991 is immutable - Hint: Could not modify commit: mzvwutvl 4397373a main | (conflict) merge + Error: Commit 0ff570e4c05e is immutable + Hint: Could not modify commit: mzvwutvl 0ff570e4 main | (conflict) merge Hint: Immutable commits are used to protect shared history. Hint: For more information, see: - https://jj-vcs.github.io/jj/latest/config/#set-of-immutable-commits @@ -346,8 +346,8 @@ fn test_rewrite_immutable_commands() { let output = work_dir.run_jj(["metaedit", "-r=main"]); insta::assert_snapshot!(output, @r#" ------- stderr ------- - Error: Commit 4397373a0991 is immutable - Hint: Could not modify commit: mzvwutvl 4397373a main | (conflict) merge + Error: Commit 0ff570e4c05e is immutable + Hint: Could not modify commit: mzvwutvl 0ff570e4 main | (conflict) merge Hint: Immutable commits are used to protect shared history. Hint: For more information, see: - https://jj-vcs.github.io/jj/latest/config/#set-of-immutable-commits @@ -360,8 +360,8 @@ fn test_rewrite_immutable_commands() { let output = work_dir.run_jj(["new", "--insert-before", "main"]); insta::assert_snapshot!(output, @r#" ------- stderr ------- - Error: Commit 4397373a0991 is immutable - Hint: Could not modify commit: mzvwutvl 4397373a main | (conflict) merge + Error: Commit 0ff570e4c05e is immutable + Hint: Could not modify commit: mzvwutvl 0ff570e4 main | (conflict) merge Hint: Immutable commits are used to protect shared history. Hint: For more information, see: - https://jj-vcs.github.io/jj/latest/config/#set-of-immutable-commits @@ -374,8 +374,8 @@ fn test_rewrite_immutable_commands() { let output = work_dir.run_jj(["new", "--insert-after", "description(b)"]); insta::assert_snapshot!(output, @r#" ------- stderr ------- - Error: Commit 4397373a0991 is immutable - Hint: Could not modify commit: mzvwutvl 4397373a main | (conflict) merge + Error: Commit 0ff570e4c05e is immutable + Hint: Could not modify commit: mzvwutvl 0ff570e4 main | (conflict) merge Hint: Immutable commits are used to protect shared history. Hint: For more information, see: - https://jj-vcs.github.io/jj/latest/config/#set-of-immutable-commits @@ -388,8 +388,8 @@ fn test_rewrite_immutable_commands() { let output = work_dir.run_jj(["parallelize", "description(b)", "main"]); insta::assert_snapshot!(output, @r#" ------- stderr ------- - Error: Commit 4397373a0991 is immutable - Hint: Could not modify commit: mzvwutvl 4397373a main | (conflict) merge + Error: Commit 0ff570e4c05e is immutable + Hint: Could not modify commit: mzvwutvl 0ff570e4 main | (conflict) merge Hint: Immutable commits are used to protect shared history. Hint: For more information, see: - https://jj-vcs.github.io/jj/latest/config/#set-of-immutable-commits @@ -402,8 +402,8 @@ fn test_rewrite_immutable_commands() { let output = work_dir.run_jj(["rebase", "-s=main", "-d=@"]); insta::assert_snapshot!(output, @r#" ------- stderr ------- - Error: Commit 4397373a0991 is immutable - Hint: Could not modify commit: mzvwutvl 4397373a main | (conflict) merge + Error: Commit 0ff570e4c05e is immutable + Hint: Could not modify commit: mzvwutvl 0ff570e4 main | (conflict) merge Hint: Immutable commits are used to protect shared history. Hint: For more information, see: - https://jj-vcs.github.io/jj/latest/config/#set-of-immutable-commits @@ -430,8 +430,8 @@ fn test_rewrite_immutable_commands() { let output = work_dir.run_jj(["rebase", "-r=main", "-d=@"]); insta::assert_snapshot!(output, @r#" ------- stderr ------- - Error: Commit 4397373a0991 is immutable - Hint: Could not modify commit: mzvwutvl 4397373a main | (conflict) merge + Error: Commit 0ff570e4c05e is immutable + Hint: Could not modify commit: mzvwutvl 0ff570e4 main | (conflict) merge Hint: Immutable commits are used to protect shared history. Hint: For more information, see: - https://jj-vcs.github.io/jj/latest/config/#set-of-immutable-commits @@ -444,8 +444,8 @@ fn test_rewrite_immutable_commands() { let output = work_dir.run_jj(["resolve", "-r=description(merge)", "file"]); insta::assert_snapshot!(output, @r#" ------- stderr ------- - Error: Commit 4397373a0991 is immutable - Hint: Could not modify commit: mzvwutvl 4397373a main | (conflict) merge + Error: Commit 0ff570e4c05e is immutable + Hint: Could not modify commit: mzvwutvl 0ff570e4 main | (conflict) merge Hint: Immutable commits are used to protect shared history. Hint: For more information, see: - https://jj-vcs.github.io/jj/latest/config/#set-of-immutable-commits @@ -458,8 +458,8 @@ fn test_rewrite_immutable_commands() { let output = work_dir.run_jj(["restore", "-c=main"]); insta::assert_snapshot!(output, @r#" ------- stderr ------- - Error: Commit 4397373a0991 is immutable - Hint: Could not modify commit: mzvwutvl 4397373a main | (conflict) merge + Error: Commit 0ff570e4c05e is immutable + Hint: Could not modify commit: mzvwutvl 0ff570e4 main | (conflict) merge Hint: Immutable commits are used to protect shared history. Hint: For more information, see: - https://jj-vcs.github.io/jj/latest/config/#set-of-immutable-commits @@ -472,8 +472,8 @@ fn test_rewrite_immutable_commands() { let output = work_dir.run_jj(["restore", "--into=main"]); insta::assert_snapshot!(output, @r#" ------- stderr ------- - Error: Commit 4397373a0991 is immutable - Hint: Could not modify commit: mzvwutvl 4397373a main | (conflict) merge + Error: Commit 0ff570e4c05e is immutable + Hint: Could not modify commit: mzvwutvl 0ff570e4 main | (conflict) merge Hint: Immutable commits are used to protect shared history. Hint: For more information, see: - https://jj-vcs.github.io/jj/latest/config/#set-of-immutable-commits @@ -486,8 +486,8 @@ fn test_rewrite_immutable_commands() { let output = work_dir.run_jj(["split", "-r=main"]); insta::assert_snapshot!(output, @r#" ------- stderr ------- - Error: Commit 4397373a0991 is immutable - Hint: Could not modify commit: mzvwutvl 4397373a main | (conflict) merge + Error: Commit 0ff570e4c05e is immutable + Hint: Could not modify commit: mzvwutvl 0ff570e4 main | (conflict) merge Hint: Immutable commits are used to protect shared history. Hint: For more information, see: - https://jj-vcs.github.io/jj/latest/config/#set-of-immutable-commits @@ -500,8 +500,8 @@ fn test_rewrite_immutable_commands() { let output = work_dir.run_jj(["split", "-B=main", "-m", "will fail", "file"]); insta::assert_snapshot!(output, @r#" ------- stderr ------- - Error: Commit 4397373a0991 is immutable - Hint: Could not modify commit: mzvwutvl 4397373a main | (conflict) merge + Error: Commit 0ff570e4c05e is immutable + Hint: Could not modify commit: mzvwutvl 0ff570e4 main | (conflict) merge Hint: Immutable commits are used to protect shared history. Hint: For more information, see: - https://jj-vcs.github.io/jj/latest/config/#set-of-immutable-commits @@ -528,8 +528,8 @@ fn test_rewrite_immutable_commands() { let output = work_dir.run_jj(["squash", "--from=main"]); insta::assert_snapshot!(output, @r#" ------- stderr ------- - Error: Commit 4397373a0991 is immutable - Hint: Could not modify commit: mzvwutvl 4397373a main | (conflict) merge + Error: Commit 0ff570e4c05e is immutable + Hint: Could not modify commit: mzvwutvl 0ff570e4 main | (conflict) merge Hint: Immutable commits are used to protect shared history. Hint: For more information, see: - https://jj-vcs.github.io/jj/latest/config/#set-of-immutable-commits @@ -542,8 +542,8 @@ fn test_rewrite_immutable_commands() { let output = work_dir.run_jj(["squash", "--into=main"]); insta::assert_snapshot!(output, @r#" ------- stderr ------- - Error: Commit 4397373a0991 is immutable - Hint: Could not modify commit: mzvwutvl 4397373a main | (conflict) merge + Error: Commit 0ff570e4c05e is immutable + Hint: Could not modify commit: mzvwutvl 0ff570e4 main | (conflict) merge Hint: Immutable commits are used to protect shared history. Hint: For more information, see: - https://jj-vcs.github.io/jj/latest/config/#set-of-immutable-commits @@ -556,8 +556,8 @@ fn test_rewrite_immutable_commands() { let output = work_dir.run_jj(["squash", "--after=main-"]); insta::assert_snapshot!(output, @r#" ------- stderr ------- - Error: Commit 4397373a0991 is immutable - Hint: Could not modify commit: mzvwutvl 4397373a main | (conflict) merge + Error: Commit 0ff570e4c05e is immutable + Hint: Could not modify commit: mzvwutvl 0ff570e4 main | (conflict) merge Hint: Immutable commits are used to protect shared history. Hint: For more information, see: - https://jj-vcs.github.io/jj/latest/config/#set-of-immutable-commits @@ -570,8 +570,8 @@ fn test_rewrite_immutable_commands() { let output = work_dir.run_jj(["squash", "--before=main"]); insta::assert_snapshot!(output, @r#" ------- stderr ------- - Error: Commit 4397373a0991 is immutable - Hint: Could not modify commit: mzvwutvl 4397373a main | (conflict) merge + Error: Commit 0ff570e4c05e is immutable + Hint: Could not modify commit: mzvwutvl 0ff570e4 main | (conflict) merge Hint: Immutable commits are used to protect shared history. Hint: For more information, see: - https://jj-vcs.github.io/jj/latest/config/#set-of-immutable-commits @@ -584,8 +584,8 @@ fn test_rewrite_immutable_commands() { let output = work_dir.run_jj(["sign", "-r=main", "--config=signing.backend=test"]); insta::assert_snapshot!(output, @r#" ------- stderr ------- - Error: Commit 4397373a0991 is immutable - Hint: Could not modify commit: mzvwutvl 4397373a main | (conflict) merge + Error: Commit 0ff570e4c05e is immutable + Hint: Could not modify commit: mzvwutvl 0ff570e4 main | (conflict) merge Hint: Immutable commits are used to protect shared history. Hint: For more information, see: - https://jj-vcs.github.io/jj/latest/config/#set-of-immutable-commits @@ -598,8 +598,8 @@ fn test_rewrite_immutable_commands() { let output = work_dir.run_jj(["unsign", "-r=main"]); insta::assert_snapshot!(output, @r#" ------- stderr ------- - Error: Commit 4397373a0991 is immutable - Hint: Could not modify commit: mzvwutvl 4397373a main | (conflict) merge + Error: Commit 0ff570e4c05e is immutable + Hint: Could not modify commit: mzvwutvl 0ff570e4 main | (conflict) merge Hint: Immutable commits are used to protect shared history. Hint: For more information, see: - https://jj-vcs.github.io/jj/latest/config/#set-of-immutable-commits diff --git a/cli/tests/test_interdiff_command.rs b/cli/tests/test_interdiff_command.rs index 65d2d4c536f..43bdca924c5 100644 --- a/cli/tests/test_interdiff_command.rs +++ b/cli/tests/test_interdiff_command.rs @@ -164,13 +164,13 @@ fn test_interdiff_conflicting() { --- a/file +++ b/file @@ -1,7 +1,1 @@ - -<<<<<<< Conflict 1 of 1 - -%%%%%%% Changes from base to side #1 + -<<<<<<< conflict 1 of 1 + -%%%%%%% side #1 compared with base --foo -+abc - -+++++++ Contents of side #2 + -+++++++ side #2 -bar - ->>>>>>> Conflict 1 of 1 ends + ->>>>>>> conflict 1 of 1 ends +def [EOF] "); diff --git a/cli/tests/test_new_command.rs b/cli/tests/test_new_command.rs index 6863a39dbe5..06ea27d3a0b 100644 --- a/cli/tests/test_new_command.rs +++ b/cli/tests/test_new_command.rs @@ -180,7 +180,7 @@ fn test_new_merge_conflicts() { let output = work_dir.run_jj(["new", "2|3"]); insta::assert_snapshot!(output, @r" ------- stderr ------- - Working copy (@) now at: vruxwmqv 5234fbf2 (conflict) (empty) (no description set) + Working copy (@) now at: vruxwmqv 78699dbc (conflict) (empty) (no description set) Parent commit (@-) : royxmykx 1b282e07 3 | 3 Parent commit (@-) : zsuskuln 7ac709e5 2 | 2 Added 0 files, modified 1 files, removed 0 files @@ -189,13 +189,13 @@ fn test_new_merge_conflicts() { [EOF] "); insta::assert_snapshot!(work_dir.read_file("file"), @r" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% royxmykx 1b282e07 compared with rlvkpnrz a93ed0a5 -1a +3a 1a - +++++++ Contents of side #2 + +++++++ zsuskuln 7ac709e5 1a 2a - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends 1b 2c "); @@ -251,7 +251,7 @@ fn test_new_merge_same_change() { let output = work_dir.run_jj(["new", "2|3", "--config=merge.same-change=keep"]); insta::assert_snapshot!(output, @r" ------- stderr ------- - Working copy (@) now at: znkkpsqq 0d655a01 (conflict) (empty) (no description set) + Working copy (@) now at: znkkpsqq 7f8ea854 (conflict) (empty) (no description set) Parent commit (@-) : royxmykx 1b9fe696 3 | 3 Parent commit (@-) : zsuskuln 829e1e90 2 | 2 Added 1 files, modified 0 files, removed 0 files @@ -261,12 +261,12 @@ fn test_new_merge_same_change() { "); insta::assert_snapshot!(work_dir.read_file("file"), @r" a - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% royxmykx 1b9fe696 compared with rlvkpnrz 2adf972b +b - +++++++ Contents of side #2 + +++++++ zsuskuln 829e1e90 b - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends "); } diff --git a/cli/tests/test_next_prev_commands.rs b/cli/tests/test_next_prev_commands.rs index da8ae16be06..e1331bf4431 100644 --- a/cli/tests/test_next_prev_commands.rs +++ b/cli/tests/test_next_prev_commands.rs @@ -938,7 +938,7 @@ fn test_next_conflict_head() { insta::assert_snapshot!(output, @r" ------- stderr ------- Error: The working copy has no descendants with conflicts - Hint: Working copy: rlvkpnrz 5f088cac (conflict) (no description set) + Hint: Working copy: rlvkpnrz 1a20d818 (conflict) (no description set) [EOF] [exit status: 1] "); diff --git a/cli/tests/test_operations.rs b/cli/tests/test_operations.rs index 01c635271f6..1041bb6b947 100644 --- a/cli/tests/test_operations.rs +++ b/cli/tests/test_operations.rs @@ -678,7 +678,7 @@ fn test_op_abandon_ancestors() { "); insta::assert_snapshot!(work_dir.run_jj(["debug", "local-working-copy", "--ignore-working-copy"]), @r#" Current operation: OperationId("1675333b7de89b5da012c696d797345bad2a6ce55a4b605e85c3897f818f05e11e8c53de19d34c2fee38a36528dc95bd2a378f72ac0877f8bec2513a68043253") - Current tree: MergedTreeId { tree_ids: Resolved(TreeId("4b825dc642cb6eb9a060e54bf8d69288fbee4904")) } + Current tree: MergedTreeId { tree_ids: Resolved(TreeId("4b825dc642cb6eb9a060e54bf8d69288fbee4904")), labels: Unlabeled } [EOF] "#); insta::assert_snapshot!(work_dir.run_jj(["op", "log"]), @r" @@ -739,7 +739,7 @@ fn test_op_abandon_ancestors() { "); insta::assert_snapshot!(work_dir.run_jj(["debug", "local-working-copy", "--ignore-working-copy"]), @r#" Current operation: OperationId("ce6a0300b7346109e75a6dcc97e3ff9e1488ce43a4073dd9eb81afb7f463b4543d3f15cf9a42a9864a4aaf6daab900b6b037dbdcb95f87422e891f7e884641aa") - Current tree: MergedTreeId { tree_ids: Resolved(TreeId("4b825dc642cb6eb9a060e54bf8d69288fbee4904")) } + Current tree: MergedTreeId { tree_ids: Resolved(TreeId("4b825dc642cb6eb9a060e54bf8d69288fbee4904")), labels: Unlabeled } [EOF] "#); insta::assert_snapshot!(work_dir.run_jj(["op", "log"]), @r" @@ -787,7 +787,7 @@ fn test_op_abandon_without_updating_working_copy() { "); insta::assert_snapshot!(work_dir.run_jj(["debug", "local-working-copy", "--ignore-working-copy"]), @r#" Current operation: OperationId("0d4bb8e4a2babc4c216be0f9bde32aeef888abebde0062aeb1c204dde5e1f476fa951fcbeceb2263cf505008ba87a834849469dede30dfc589f37d5073aedfbe") - Current tree: MergedTreeId { tree_ids: Resolved(TreeId("4b825dc642cb6eb9a060e54bf8d69288fbee4904")) } + Current tree: MergedTreeId { tree_ids: Resolved(TreeId("4b825dc642cb6eb9a060e54bf8d69288fbee4904")), labels: Unlabeled } [EOF] "#); insta::assert_snapshot!(work_dir.run_jj(["op", "log", "-n1", "--ignore-working-copy"]), @r" @@ -809,7 +809,7 @@ fn test_op_abandon_without_updating_working_copy() { "); insta::assert_snapshot!(work_dir.run_jj(["debug", "local-working-copy", "--ignore-working-copy"]), @r#" Current operation: OperationId("0d4bb8e4a2babc4c216be0f9bde32aeef888abebde0062aeb1c204dde5e1f476fa951fcbeceb2263cf505008ba87a834849469dede30dfc589f37d5073aedfbe") - Current tree: MergedTreeId { tree_ids: Resolved(TreeId("4b825dc642cb6eb9a060e54bf8d69288fbee4904")) } + Current tree: MergedTreeId { tree_ids: Resolved(TreeId("4b825dc642cb6eb9a060e54bf8d69288fbee4904")), labels: Unlabeled } [EOF] "#); insta::assert_snapshot!(work_dir.run_jj(["op", "log", "-n1", "--ignore-working-copy"]), @r" diff --git a/cli/tests/test_repo_change_report.rs b/cli/tests/test_repo_change_report.rs index ef5dc5303b8..ec2b9c514b9 100644 --- a/cli/tests/test_repo_change_report.rs +++ b/cli/tests/test_repo_change_report.rs @@ -28,17 +28,17 @@ fn test_report_conflicts() { work_dir.run_jj(["commit", "-m=C"]).success(); let output = work_dir.run_jj(["rebase", "-s=description(B)", "-d=root()"]); - insta::assert_snapshot!(output, @r###" + insta::assert_snapshot!(output, @r" ------- stderr ------- Rebased 3 commits to destination - Working copy (@) now at: zsuskuln dd37d4a5 (conflict) (empty) (no description set) - Parent commit (@-) : kkmpptxz c7f5d6e5 (conflict) C + Working copy (@) now at: zsuskuln c04a9550 (conflict) (empty) (no description set) + Parent commit (@-) : kkmpptxz 3b9d1435 (conflict) C Added 0 files, modified 1 files, removed 0 files Warning: There are unresolved conflicts at these paths: file 2-sided conflict including 1 deletion New conflicts appeared in 2 commits: - kkmpptxz c7f5d6e5 (conflict) C - rlvkpnrz 032a8668 (conflict) B + kkmpptxz 3b9d1435 (conflict) C + rlvkpnrz db3bb68b (conflict) B Hint: To resolve the conflicts, start by creating a commit on top of the first conflicted commit: jj new rlvkpnrz @@ -46,7 +46,7 @@ fn test_report_conflicts() { Once the conflicts are resolved, you can inspect the result with `jj diff`. Then run `jj squash` to move the resolution into the conflicted commit. [EOF] - "###); + "); let output = work_dir.run_jj(["rebase", "-d=description(A)"]); insta::assert_snapshot!(output, @r" @@ -61,18 +61,18 @@ fn test_report_conflicts() { // Can get hint about multiple root commits let output = work_dir.run_jj(["rebase", "-r=description(B)", "-d=root()"]); - insta::assert_snapshot!(output, @r###" + insta::assert_snapshot!(output, @r" ------- stderr ------- Rebased 1 commits to destination Rebased 2 descendant commits - Working copy (@) now at: zsuskuln 5b511768 (conflict) (empty) (no description set) - Parent commit (@-) : kkmpptxz 0252a7f0 (conflict) C + Working copy (@) now at: zsuskuln a974963d (conflict) (empty) (no description set) + Parent commit (@-) : kkmpptxz f143f8b6 (conflict) C Added 0 files, modified 1 files, removed 0 files Warning: There are unresolved conflicts at these paths: file 2-sided conflict New conflicts appeared in 2 commits: - kkmpptxz 0252a7f0 (conflict) C - rlvkpnrz fcfd7304 (conflict) B + kkmpptxz f143f8b6 (conflict) C + rlvkpnrz 83784859 (conflict) B Hint: To resolve the conflicts, start by creating a commit on top of one of the first conflicted commits: jj new kkmpptxz @@ -81,14 +81,14 @@ fn test_report_conflicts() { Once the conflicts are resolved, you can inspect the result with `jj diff`. Then run `jj squash` to move the resolution into the conflicted commit. [EOF] - "###); + "); // Resolve one of the conflicts by (mostly) following the instructions let output = work_dir.run_jj(["new", "rlvkpnrzqnoo"]); insta::assert_snapshot!(output, @r" ------- stderr ------- - Working copy (@) now at: vruxwmqv 55514f4e (conflict) (empty) (no description set) - Parent commit (@-) : rlvkpnrz fcfd7304 (conflict) B + Working copy (@) now at: vruxwmqv 415242f0 (conflict) (empty) (no description set) + Parent commit (@-) : rlvkpnrz 83784859 (conflict) B Added 0 files, modified 1 files, removed 0 files Warning: There are unresolved conflicts at these paths: file 2-sided conflict including 1 deletion @@ -123,19 +123,19 @@ fn test_report_conflicts_with_divergent_commits() { .success(); let output = work_dir.run_jj(["rebase", "-s=description(B)", "-d=root()"]); - insta::assert_snapshot!(output, @r###" + insta::assert_snapshot!(output, @r" ------- stderr ------- Concurrent modification detected, resolving automatically. Rebased 3 commits to destination - Working copy (@) now at: zsuskuln?? 08a31f4f (conflict) C2 - Parent commit (@-) : kkmpptxz 099d6624 (conflict) B + Working copy (@) now at: zsuskuln?? fa7146c9 (conflict) C2 + Parent commit (@-) : kkmpptxz 0638fb86 (conflict) B Added 0 files, modified 1 files, removed 0 files Warning: There are unresolved conflicts at these paths: file 2-sided conflict including 1 deletion New conflicts appeared in 3 commits: - zsuskuln?? df34134a (conflict) C3 - zsuskuln?? 08a31f4f (conflict) C2 - kkmpptxz 099d6624 (conflict) B + zsuskuln?? cdc36d13 (conflict) C3 + zsuskuln?? fa7146c9 (conflict) C2 + kkmpptxz 0638fb86 (conflict) B Hint: To resolve the conflicts, start by creating a commit on top of the first conflicted commit: jj new kkmpptxz @@ -143,7 +143,7 @@ fn test_report_conflicts_with_divergent_commits() { Once the conflicts are resolved, you can inspect the result with `jj diff`. Then run `jj squash` to move the resolution into the conflicted commit. [EOF] - "###); + "); let output = work_dir.run_jj(["rebase", "-d=description(A)"]); insta::assert_snapshot!(output, @r" @@ -158,16 +158,16 @@ fn test_report_conflicts_with_divergent_commits() { // Same thing when rebasing the divergent commits one at a time let output = work_dir.run_jj(["rebase", "-s=description(C2)", "-d=root()"]); - insta::assert_snapshot!(output, @r###" + insta::assert_snapshot!(output, @r" ------- stderr ------- Rebased 1 commits to destination - Working copy (@) now at: zsuskuln?? dfe73891 (conflict) C2 + Working copy (@) now at: zsuskuln?? bfd96f94 (conflict) C2 Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set) Added 0 files, modified 1 files, removed 0 files Warning: There are unresolved conflicts at these paths: file 2-sided conflict including 1 deletion New conflicts appeared in 1 commits: - zsuskuln?? dfe73891 (conflict) C2 + zsuskuln?? bfd96f94 (conflict) C2 Hint: To resolve the conflicts, start by creating a commit on top of the conflicted commit: jj new zsuskuln @@ -175,14 +175,14 @@ fn test_report_conflicts_with_divergent_commits() { Once the conflicts are resolved, you can inspect the result with `jj diff`. Then run `jj squash` to move the resolution into the conflicted commit. [EOF] - "###); + "); let output = work_dir.run_jj(["rebase", "-s=description(C3)", "-d=root()"]); - insta::assert_snapshot!(output, @r###" + insta::assert_snapshot!(output, @r" ------- stderr ------- Rebased 1 commits to destination New conflicts appeared in 1 commits: - zsuskuln?? 02834578 (conflict) C3 + zsuskuln?? a8483af6 (conflict) C3 Hint: To resolve the conflicts, start by creating a commit on top of the conflicted commit: jj new zsuskuln @@ -190,7 +190,7 @@ fn test_report_conflicts_with_divergent_commits() { Once the conflicts are resolved, you can inspect the result with `jj diff`. Then run `jj squash` to move the resolution into the conflicted commit. [EOF] - "###); + "); let output = work_dir.run_jj(["rebase", "-s=description(C2)", "-d=description(B)"]); insta::assert_snapshot!(output, @r" @@ -234,14 +234,14 @@ fn test_report_conflicts_with_resolving_conflicts_hint_disabled() { insta::assert_snapshot!(output, @r" ------- stderr ------- Rebased 3 commits to destination - Working copy (@) now at: zsuskuln dd37d4a5 (conflict) (empty) (no description set) - Parent commit (@-) : kkmpptxz c7f5d6e5 (conflict) C + Working copy (@) now at: zsuskuln c04a9550 (conflict) (empty) (no description set) + Parent commit (@-) : kkmpptxz 3b9d1435 (conflict) C Added 0 files, modified 1 files, removed 0 files Warning: There are unresolved conflicts at these paths: file 2-sided conflict including 1 deletion New conflicts appeared in 2 commits: - kkmpptxz c7f5d6e5 (conflict) C - rlvkpnrz 032a8668 (conflict) B + kkmpptxz 3b9d1435 (conflict) C + rlvkpnrz db3bb68b (conflict) B [EOF] "); } diff --git a/cli/tests/test_resolve_command.rs b/cli/tests/test_resolve_command.rs index adf12404072..01dd45b6e85 100644 --- a/cli/tests/test_resolve_command.rs +++ b/cli/tests/test_resolve_command.rs @@ -53,13 +53,13 @@ fn test_resolution() { [EOF] "); insta::assert_snapshot!(work_dir.read_file("file"), @r" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% zsuskuln 45537d53 compared with rlvkpnrz 1792382a -base +a - +++++++ Contents of side #2 + +++++++ royxmykx 89d1b299 b - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends "); let setup_opid = work_dir.current_operation_id(); @@ -87,13 +87,13 @@ fn test_resolution() { --- a/file +++ b/file @@ -1,7 +1,1 @@ - -<<<<<<< Conflict 1 of 1 - -%%%%%%% Changes from base to side #1 + -<<<<<<< conflict 1 of 1 + -%%%%%%% zsuskuln 45537d53 compared with rlvkpnrz 1792382a --base -+a - -+++++++ Contents of side #2 + -+++++++ royxmykx 89d1b299 -b - ->>>>>>> Conflict 1 of 1 ends + ->>>>>>> conflict 1 of 1 ends +resolution [EOF] "); @@ -127,13 +127,13 @@ fn test_resolution() { --- a/file +++ b/file @@ -1,7 +1,1 @@ - -<<<<<<< Conflict 1 of 1 - -%%%%%%% Changes from base to side #1 + -<<<<<<< conflict 1 of 1 + -%%%%%%% zsuskuln 45537d53 compared with rlvkpnrz 1792382a --base -+a - -+++++++ Contents of side #2 + -+++++++ royxmykx 89d1b299 -b - ->>>>>>> Conflict 1 of 1 ends + ->>>>>>> conflict 1 of 1 ends +resolution [EOF] "); @@ -161,13 +161,13 @@ fn test_resolution() { .success(); insta::assert_snapshot!( std::fs::read_to_string(test_env.env_root().join("editor1")).unwrap(), @r" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% zsuskuln 45537d53 compared with rlvkpnrz 1792382a -base +a - +++++++ Contents of side #2 + +++++++ royxmykx 89d1b299 b - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends "); insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" diff --git a/file b/file @@ -175,13 +175,13 @@ fn test_resolution() { --- a/file +++ b/file @@ -1,7 +1,1 @@ - -<<<<<<< Conflict 1 of 1 - -%%%%%%% Changes from base to side #1 + -<<<<<<< conflict 1 of 1 + -%%%%%%% zsuskuln 45537d53 compared with rlvkpnrz 1792382a --base -+a - -+++++++ Contents of side #2 + -+++++++ royxmykx 89d1b299 -b - ->>>>>>> Conflict 1 of 1 ends + ->>>>>>> conflict 1 of 1 ends +resolution [EOF] "); @@ -212,17 +212,17 @@ fn test_resolution() { "resolve", "--config=merge-tools.fake-editor.merge-tool-edits-conflict-markers=true", ]); - insta::assert_snapshot!(output, @r###" + insta::assert_snapshot!(output, @r" ------- stderr ------- Resolving conflicts in: file - Working copy (@) now at: vruxwmqv 0d40d2b8 conflict | (conflict) conflict + Working copy (@) now at: vruxwmqv 41273029 conflict | (conflict) conflict Parent commit (@-) : zsuskuln 45537d53 a | a Parent commit (@-) : royxmykx 89d1b299 b | b Added 0 files, modified 1 files, removed 0 files Warning: There are unresolved conflicts at these paths: file 2-sided conflict New conflicts appeared in 1 commits: - vruxwmqv 0d40d2b8 conflict | (conflict) conflict + vruxwmqv 41273029 conflict | (conflict) conflict Hint: To resolve the conflicts, start by creating a commit on top of the conflicted commit: jj new vruxwmqv @@ -230,16 +230,16 @@ fn test_resolution() { Once the conflicts are resolved, you can inspect the result with `jj diff`. Then run `jj squash` to move the resolution into the conflicted commit. [EOF] - "###); + "); insta::assert_snapshot!( std::fs::read_to_string(test_env.env_root().join("editor2")).unwrap(), @r" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% zsuskuln 45537d53 compared with rlvkpnrz 1792382a -base +a - +++++++ Contents of side #2 + +++++++ royxmykx 89d1b299 b - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends "); // Note the "Modified" below insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" @@ -247,16 +247,16 @@ fn test_resolution() { --- a/file +++ b/file @@ -1,7 +1,7 @@ - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% zsuskuln 45537d53 compared with rlvkpnrz 1792382a --base -+a +-some ++fake - +++++++ Contents of side #2 + +++++++ royxmykx 89d1b299 -b +conflict - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends [EOF] "); insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" @@ -306,13 +306,13 @@ fn test_resolution() { --- a/file +++ b/file @@ -1,7 +1,7 @@ - -<<<<<<< Conflict 1 of 1 - -%%%%%%% Changes from base to side #1 + -<<<<<<< conflict 1 of 1 + -%%%%%%% zsuskuln 45537d53 compared with rlvkpnrz 1792382a --base -+a - -+++++++ Contents of side #2 + -+++++++ royxmykx 89d1b299 -b - ->>>>>>> Conflict 1 of 1 ends + ->>>>>>> conflict 1 of 1 ends +<<<<<<< +%%%%%%% +-some @@ -356,17 +356,17 @@ fn test_resolution() { "--config=merge-tools.fake-editor.merge-tool-edits-conflict-markers=true", "--config=merge-tools.fake-editor.conflict-marker-style=git", ]); - insta::assert_snapshot!(output, @r###" + insta::assert_snapshot!(output, @r" ------- stderr ------- Resolving conflicts in: file - Working copy (@) now at: vruxwmqv d5f058ec conflict | (conflict) conflict + Working copy (@) now at: vruxwmqv d811835a conflict | (conflict) conflict Parent commit (@-) : zsuskuln 45537d53 a | a Parent commit (@-) : royxmykx 89d1b299 b | b Added 0 files, modified 1 files, removed 0 files Warning: There are unresolved conflicts at these paths: file 2-sided conflict New conflicts appeared in 1 commits: - vruxwmqv d5f058ec conflict | (conflict) conflict + vruxwmqv d811835a conflict | (conflict) conflict Hint: To resolve the conflicts, start by creating a commit on top of the conflicted commit: jj new vruxwmqv @@ -374,32 +374,32 @@ fn test_resolution() { Once the conflicts are resolved, you can inspect the result with `jj diff`. Then run `jj squash` to move the resolution into the conflicted commit. [EOF] - "###); + "); insta::assert_snapshot!( std::fs::read_to_string(test_env.env_root().join("editor4")).unwrap(), @r" - <<<<<<< Side #1 (Conflict 1 of 1) + <<<<<<< zsuskuln 45537d53 (conflict 1 of 1) a - ||||||| Base + ||||||| rlvkpnrz 1792382a base ======= b - >>>>>>> Side #2 (Conflict 1 of 1 ends) + >>>>>>> royxmykx 89d1b299 (conflict 1 of 1 ends) "); insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" diff --git a/file b/file --- a/file +++ b/file @@ -1,7 +1,7 @@ - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% zsuskuln 45537d53 compared with rlvkpnrz 1792382a --base -+a +-fake ++some - +++++++ Contents of side #2 + +++++++ royxmykx 89d1b299 -b +conflict - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends [EOF] "); insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" @@ -435,17 +435,17 @@ fn test_resolution() { "resolve", "--config=merge-tools.fake-editor.merge-conflict-exit-codes=[1]", ]); - insta::assert_snapshot!(output, @r###" + insta::assert_snapshot!(output, @r" ------- stderr ------- Resolving conflicts in: file - Working copy (@) now at: vruxwmqv 6c205356 conflict | (conflict) conflict + Working copy (@) now at: vruxwmqv 5ba19d9c conflict | (conflict) conflict Parent commit (@-) : zsuskuln 45537d53 a | a Parent commit (@-) : royxmykx 89d1b299 b | b Added 0 files, modified 1 files, removed 0 files Warning: There are unresolved conflicts at these paths: file 2-sided conflict New conflicts appeared in 1 commits: - vruxwmqv 6c205356 conflict | (conflict) conflict + vruxwmqv 5ba19d9c conflict | (conflict) conflict Hint: To resolve the conflicts, start by creating a commit on top of the conflicted commit: jj new vruxwmqv @@ -453,7 +453,7 @@ fn test_resolution() { Once the conflicts are resolved, you can inspect the result with `jj diff`. Then run `jj squash` to move the resolution into the conflicted commit. [EOF] - "###); + "); insta::assert_snapshot!( std::fs::read_to_string(test_env.env_root().join("editor5")).unwrap(), @""); insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" @@ -461,16 +461,16 @@ fn test_resolution() { --- a/file +++ b/file @@ -1,7 +1,7 @@ - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% zsuskuln 45537d53 compared with rlvkpnrz 1792382a --base -+a +-fake ++some - +++++++ Contents of side #2 + +++++++ royxmykx 89d1b299 -b +conflict - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends [EOF] "); insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" @@ -569,13 +569,13 @@ fn test_normal_conflict_input_files() { [EOF] "); insta::assert_snapshot!(work_dir.read_file("file"), @r" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% zsuskuln 45537d53 compared with rlvkpnrz 1792382a -base +a - +++++++ Contents of side #2 + +++++++ royxmykx 89d1b299 b - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends "); check_resolve_produces_input_file(&mut test_env, "repo", "file", "base", "base\n"); @@ -609,12 +609,12 @@ fn test_baseless_conflict_input_files() { [EOF] "); insta::assert_snapshot!(work_dir.read_file("file"), @r" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% zsuskuln e3c7222d compared with rlvkpnrz 2308e5a2 +a - +++++++ Contents of side #2 + +++++++ royxmykx 1f2c13ec b - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends "); check_resolve_produces_input_file(&mut test_env, "repo", "file", "base", ""); @@ -691,22 +691,22 @@ fn test_simplify_conflict_sides() { [EOF] "); insta::assert_snapshot!(work_dir.read_file("fileA"), @r" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% zsuskuln f302fbd1 compared with rlvkpnrz ca4643d3 -base +1 - +++++++ Contents of side #2 + +++++++ royxmykx 128a2559 2 - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends "); insta::assert_snapshot!(work_dir.read_file("fileB"), @r" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% vruxwmqv 5be2d37a compared with rlvkpnrz ca4643d3 -base +1 - +++++++ Contents of side #2 + +++++++ znkkpsqq bd8e6328 2 - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends "); // Conflict should be simplified before being handled by external merge tool. @@ -724,13 +724,13 @@ fn test_simplify_conflict_sides() { editor_script, indoc! {" write - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% side #1 compared with base -base_edited +1_edited - +++++++ Contents of side #2 + +++++++ side #2 2_edited - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends "}, ) .unwrap(); @@ -740,18 +740,18 @@ fn test_simplify_conflict_sides() { "--config=merge-tools.fake-editor.merge-tool-edits-conflict-markers=true", "fileB", ]); - insta::assert_snapshot!(output, @r###" + insta::assert_snapshot!(output, @r" ------- stderr ------- Resolving conflicts in: fileB - Working copy (@) now at: nkmrtpmo 25c5dd0b conflict | (conflict) conflict - Parent commit (@-) : kmkuslsw ccb05364 conflictA | (conflict) (empty) conflictA - Parent commit (@-) : lylxulpl d9bc60cb conflictB | (conflict) (empty) conflictB + Working copy (@) now at: nkmrtpmo d85670d9 conflict | (conflict) conflict + Parent commit (@-) : kmkuslsw 31088e28 conflictA | (conflict) (empty) conflictA + Parent commit (@-) : lylxulpl 0b4c6dc6 conflictB | (conflict) (empty) conflictB Added 0 files, modified 1 files, removed 0 files Warning: There are unresolved conflicts at these paths: fileA 2-sided conflict fileB 2-sided conflict New conflicts appeared in 1 commits: - nkmrtpmo 25c5dd0b conflict | (conflict) conflict + nkmrtpmo d85670d9 conflict | (conflict) conflict Hint: To resolve the conflicts, start by creating a commit on top of the conflicted commit: jj new nkmrtpmo @@ -759,15 +759,15 @@ fn test_simplify_conflict_sides() { Once the conflicts are resolved, you can inspect the result with `jj diff`. Then run `jj squash` to move the resolution into the conflicted commit. [EOF] - "###); + "); insta::assert_snapshot!(work_dir.read_file("fileB"), @r" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% vruxwmqv 5be2d37a compared with rlvkpnrz ca4643d3 -base_edited +1_edited - +++++++ Contents of side #2 + +++++++ znkkpsqq bd8e6328 2_edited - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends "); insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" fileA 2-sided conflict @@ -803,12 +803,12 @@ fn test_edit_delete_conflict_input_files() { [EOF] "); insta::assert_snapshot!(work_dir.read_file("file"), @r" - <<<<<<< Conflict 1 of 1 - +++++++ Contents of side #1 + <<<<<<< conflict 1 of 1 + +++++++ zsuskuln 45537d53 a - %%%%%%% Changes from base to side #2 + %%%%%%% royxmykx d213fd81 compared with rlvkpnrz 1792382a -base - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends "); check_resolve_produces_input_file(&mut test_env, "repo", "file", "base", "base\n"); @@ -950,23 +950,23 @@ fn test_resolve_conflicts_with_executable() { [EOF] "); insta::assert_snapshot!(work_dir.read_file("file1"), @r" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% mzvwutvl 86f7f0e3 compared with rlvkpnrz b90abfa7 -base1 +a1 - +++++++ Contents of side #2 + +++++++ yqosqzyt 36361412 b1 - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends " ); insta::assert_snapshot!(work_dir.read_file("file2"), @r" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% mzvwutvl 86f7f0e3 compared with rlvkpnrz b90abfa7 -base2 +a2 - +++++++ Contents of side #2 + +++++++ yqosqzyt 36361412 b2 - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends " ); let setup_opid = work_dir.current_operation_id(); @@ -974,17 +974,17 @@ fn test_resolve_conflicts_with_executable() { // Test resolving the conflict in "file1", which should produce an executable std::fs::write(&editor_script, b"write\nresolution1\n").unwrap(); let output = work_dir.run_jj(["resolve", "file1"]); - insta::assert_snapshot!(output, @r###" + insta::assert_snapshot!(output, @r" ------- stderr ------- Resolving conflicts in: file1 - Working copy (@) now at: znkkpsqq 8ab9c54e conflict | (conflict) conflict + Working copy (@) now at: znkkpsqq 0d6ddd42 conflict | (conflict) conflict Parent commit (@-) : mzvwutvl 86f7f0e3 a | a Parent commit (@-) : yqosqzyt 36361412 b | b Added 0 files, modified 1 files, removed 0 files Warning: There are unresolved conflicts at these paths: file2 2-sided conflict including an executable New conflicts appeared in 1 commits: - znkkpsqq 8ab9c54e conflict | (conflict) conflict + znkkpsqq 0d6ddd42 conflict | (conflict) conflict Hint: To resolve the conflicts, start by creating a commit on top of the conflicted commit: jj new znkkpsqq @@ -992,20 +992,20 @@ fn test_resolve_conflicts_with_executable() { Once the conflicts are resolved, you can inspect the result with `jj diff`. Then run `jj squash` to move the resolution into the conflicted commit. [EOF] - "###); + "); insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" diff --git a/file1 b/file1 index 0000000000..95cc18629d 100755 --- a/file1 +++ b/file1 @@ -1,7 +1,1 @@ - -<<<<<<< Conflict 1 of 1 - -%%%%%%% Changes from base to side #1 + -<<<<<<< conflict 1 of 1 + -%%%%%%% mzvwutvl 86f7f0e3 compared with rlvkpnrz b90abfa7 --base1 -+a1 - -+++++++ Contents of side #2 + -+++++++ yqosqzyt 36361412 -b1 - ->>>>>>> Conflict 1 of 1 ends + ->>>>>>> conflict 1 of 1 ends +resolution1 [EOF] "); @@ -1018,17 +1018,17 @@ fn test_resolve_conflicts_with_executable() { work_dir.run_jj(["op", "restore", &setup_opid]).success(); std::fs::write(&editor_script, b"write\nresolution2\n").unwrap(); let output = work_dir.run_jj(["resolve", "file2"]); - insta::assert_snapshot!(output, @r###" + insta::assert_snapshot!(output, @r" ------- stderr ------- Resolving conflicts in: file2 - Working copy (@) now at: znkkpsqq d47830a6 conflict | (conflict) conflict + Working copy (@) now at: znkkpsqq 1a70b1c6 conflict | (conflict) conflict Parent commit (@-) : mzvwutvl 86f7f0e3 a | a Parent commit (@-) : yqosqzyt 36361412 b | b Added 0 files, modified 1 files, removed 0 files Warning: There are unresolved conflicts at these paths: file1 2-sided conflict including an executable New conflicts appeared in 1 commits: - znkkpsqq d47830a6 conflict | (conflict) conflict + znkkpsqq 1a70b1c6 conflict | (conflict) conflict Hint: To resolve the conflicts, start by creating a commit on top of the conflicted commit: jj new znkkpsqq @@ -1036,20 +1036,20 @@ fn test_resolve_conflicts_with_executable() { Once the conflicts are resolved, you can inspect the result with `jj diff`. Then run `jj squash` to move the resolution into the conflicted commit. [EOF] - "###); + "); insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" diff --git a/file2 b/file2 index 0000000000..775f078581 100755 --- a/file2 +++ b/file2 @@ -1,7 +1,1 @@ - -<<<<<<< Conflict 1 of 1 - -%%%%%%% Changes from base to side #1 + -<<<<<<< conflict 1 of 1 + -%%%%%%% mzvwutvl 86f7f0e3 compared with rlvkpnrz b90abfa7 --base2 -+a2 - -+++++++ Contents of side #2 + -+++++++ yqosqzyt 36361412 -b2 - ->>>>>>> Conflict 1 of 1 ends + ->>>>>>> conflict 1 of 1 ends +resolution2 [EOF] "); @@ -1075,26 +1075,26 @@ fn test_resolve_conflicts_with_executable() { --- a/file1 +++ b/file1 @@ -1,7 +1,1 @@ - -<<<<<<< Conflict 1 of 1 - -%%%%%%% Changes from base to side #1 + -<<<<<<< conflict 1 of 1 + -%%%%%%% mzvwutvl 86f7f0e3 compared with rlvkpnrz b90abfa7 --base1 -+a1 - -+++++++ Contents of side #2 + -+++++++ yqosqzyt 36361412 -b1 - ->>>>>>> Conflict 1 of 1 ends + ->>>>>>> conflict 1 of 1 ends +a1 diff --git a/file2 b/file2 index 0000000000..c1827f07e1 100755 --- a/file2 +++ b/file2 @@ -1,7 +1,1 @@ - -<<<<<<< Conflict 1 of 1 - -%%%%%%% Changes from base to side #1 + -<<<<<<< conflict 1 of 1 + -%%%%%%% mzvwutvl 86f7f0e3 compared with rlvkpnrz b90abfa7 --base2 -+a2 - -+++++++ Contents of side #2 + -+++++++ yqosqzyt 36361412 -b2 - ->>>>>>> Conflict 1 of 1 ends + ->>>>>>> conflict 1 of 1 ends +a2 [EOF] "); @@ -1116,25 +1116,25 @@ fn test_resolve_conflicts_with_executable() { --- a/file1 +++ b/file1 @@ -1,7 +1,1 @@ - -<<<<<<< Conflict 1 of 1 - -%%%%%%% Changes from base to side #1 + -<<<<<<< conflict 1 of 1 + -%%%%%%% mzvwutvl 86f7f0e3 compared with rlvkpnrz b90abfa7 --base1 -+a1 - -+++++++ Contents of side #2 + -+++++++ yqosqzyt 36361412 b1 - ->>>>>>> Conflict 1 of 1 ends + ->>>>>>> conflict 1 of 1 ends diff --git a/file2 b/file2 index 0000000000..e6bfff5c1d 100755 --- a/file2 +++ b/file2 @@ -1,7 +1,1 @@ - -<<<<<<< Conflict 1 of 1 - -%%%%%%% Changes from base to side #1 + -<<<<<<< conflict 1 of 1 + -%%%%%%% mzvwutvl 86f7f0e3 compared with rlvkpnrz b90abfa7 --base2 -+a2 - -+++++++ Contents of side #2 + -+++++++ yqosqzyt 36361412 b2 - ->>>>>>> Conflict 1 of 1 ends + ->>>>>>> conflict 1 of 1 ends [EOF] "); } @@ -1204,7 +1204,7 @@ fn test_resolve_change_delete_executable() { [EOF] "); insta::assert_snapshot!(work_dir.run_jj(["log", "--git"]), @r" - @ kmkuslsw test.user@example.com 2001-02-03 08:05:18 conflict 7a7ac759 conflict + @ kmkuslsw test.user@example.com 2001-02-03 08:05:18 conflict fdb00ed4 conflict ├─╮ (empty) conflict │ ○ vruxwmqv test.user@example.com 2001-02-03 08:05:17 b 888b6cc3 │ │ b @@ -1305,7 +1305,7 @@ fn test_resolve_change_delete_executable() { insta::assert_snapshot!(output, @r" ------- stderr ------- Resolving conflicts in: file2 - Working copy (@) now at: kmkuslsw 1323520e conflict | (conflict) conflict + Working copy (@) now at: kmkuslsw d54d1012 conflict | (conflict) conflict Parent commit (@-) : mzvwutvl e2d3924b a | a Parent commit (@-) : vruxwmqv 888b6cc3 b | b Added 0 files, modified 1 files, removed 0 files @@ -1336,7 +1336,7 @@ fn test_resolve_change_delete_executable() { let output = work_dir.run_jj(["resolve", "file4", "--tool=:ours"]); insta::assert_snapshot!(output, @r" ------- stderr ------- - Working copy (@) now at: kmkuslsw 630e8689 conflict | (conflict) conflict + Working copy (@) now at: kmkuslsw 92e3e5c3 conflict | (conflict) conflict Parent commit (@-) : mzvwutvl e2d3924b a | a Parent commit (@-) : vruxwmqv 888b6cc3 b | b Added 0 files, modified 1 files, removed 0 files @@ -1386,14 +1386,14 @@ fn test_pass_path_argument() { [EOF] "); insta::assert_snapshot!(work_dir.read_file("file"), @r" - <<<<<<< Conflict 1 of 1 - +++++++ Contents of side #1 + <<<<<<< conflict 1 of 1 + +++++++ zsuskuln 45537d53 a - ------- Contents of base + ------- rlvkpnrz 1792382a base - +++++++ Contents of side #2 + +++++++ royxmykx 89d1b299 b - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends " ); @@ -1427,14 +1427,14 @@ fn test_pass_path_argument() { --- a/file +++ b/file @@ -1,8 +1,1 @@ - -<<<<<<< Conflict 1 of 1 - -+++++++ Contents of side #1 + -<<<<<<< conflict 1 of 1 + -+++++++ zsuskuln 45537d53 -a - -------- Contents of base + -------- rlvkpnrz 1792382a -base - -+++++++ Contents of side #2 + -+++++++ royxmykx 89d1b299 -b - ->>>>>>> Conflict 1 of 1 ends + ->>>>>>> conflict 1 of 1 ends +resolution [EOF] "); @@ -1460,14 +1460,14 @@ fn test_resolve_long_conflict_markers() { [EOF] "); insta::assert_snapshot!(work_dir.read_file("file"), @r" - <<<<<<<<<<< Conflict 1 of 1 - +++++++++++ Contents of side #1 + <<<<<<<<<<< conflict 1 of 1 + +++++++++++ zsuskuln 10d994ef <<<<<<< a - ----------- Contents of base + ----------- rlvkpnrz 04dceede ======= base - +++++++++++ Contents of side #2 + +++++++++++ royxmykx 7f215575 >>>>>>> b - >>>>>>>>>>> Conflict 1 of 1 ends + >>>>>>>>>>> conflict 1 of 1 ends " ); let setup_opid = work_dir.current_operation_id(); @@ -1493,17 +1493,17 @@ fn test_resolve_long_conflict_markers() { ) .unwrap(); let output = work_dir.run_jj(["resolve"]); - insta::assert_snapshot!(output, @r###" + insta::assert_snapshot!(output, @r" ------- stderr ------- Resolving conflicts in: file - Working copy (@) now at: vruxwmqv 1e254ee3 conflict | (conflict) conflict + Working copy (@) now at: vruxwmqv 3d6d3b1a conflict | (conflict) conflict Parent commit (@-) : zsuskuln 10d994ef a | a Parent commit (@-) : royxmykx 7f215575 b | b Added 0 files, modified 1 files, removed 0 files Warning: There are unresolved conflicts at these paths: file 2-sided conflict New conflicts appeared in 1 commits: - vruxwmqv 1e254ee3 conflict | (conflict) conflict + vruxwmqv 3d6d3b1a conflict | (conflict) conflict Hint: To resolve the conflicts, start by creating a commit on top of the conflicted commit: jj new vruxwmqv @@ -1511,28 +1511,28 @@ fn test_resolve_long_conflict_markers() { Once the conflicts are resolved, you can inspect the result with `jj diff`. Then run `jj squash` to move the resolution into the conflicted commit. [EOF] - "###); + "); insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" diff --git a/file b/file --- a/file +++ b/file @@ -1,8 +1,8 @@ - -<<<<<<<<<<< Conflict 1 of 1 - -+++++++++++ Contents of side #1 + -<<<<<<<<<<< conflict 1 of 1 + -+++++++++++ zsuskuln 10d994ef -<<<<<<< a - ------------ Contents of base + ------------ rlvkpnrz 04dceede -======= base - -+++++++++++ Contents of side #2 + -+++++++++++ royxmykx 7f215575 ->>>>>>> b - ->>>>>>>>>>> Conflict 1 of 1 ends - +<<<<<<< Conflict 1 of 1 - ++++++++ Contents of side #1 + ->>>>>>>>>>> conflict 1 of 1 ends + +<<<<<<< conflict 1 of 1 + ++++++++ zsuskuln 10d994ef +A - +------- Contents of base + +------- rlvkpnrz 04dceede +BASE - ++++++++ Contents of side #2 + ++++++++ royxmykx 7f215575 +B - +>>>>>>> Conflict 1 of 1 ends + +>>>>>>> conflict 1 of 1 ends [EOF] "); insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" @@ -1563,17 +1563,17 @@ fn test_resolve_long_conflict_markers() { "resolve", "--config=merge-tools.fake-editor.merge-tool-edits-conflict-markers=true", ]); - insta::assert_snapshot!(output, @r###" + insta::assert_snapshot!(output, @r" ------- stderr ------- Resolving conflicts in: file - Working copy (@) now at: vruxwmqv 2481a401 conflict | (conflict) conflict + Working copy (@) now at: vruxwmqv 4fec0071 conflict | (conflict) conflict Parent commit (@-) : zsuskuln 10d994ef a | a Parent commit (@-) : royxmykx 7f215575 b | b Added 0 files, modified 1 files, removed 0 files Warning: There are unresolved conflicts at these paths: file 2-sided conflict New conflicts appeared in 1 commits: - vruxwmqv 2481a401 conflict | (conflict) conflict + vruxwmqv 4fec0071 conflict | (conflict) conflict Hint: To resolve the conflicts, start by creating a commit on top of the conflicted commit: jj new vruxwmqv @@ -1581,34 +1581,34 @@ fn test_resolve_long_conflict_markers() { Once the conflicts are resolved, you can inspect the result with `jj diff`. Then run `jj squash` to move the resolution into the conflicted commit. [EOF] - "###); + "); insta::assert_snapshot!( std::fs::read_to_string(test_env.env_root().join("editor")).unwrap(), @r" - <<<<<<<<<<< Conflict 1 of 1 - +++++++++++ Contents of side #1 + <<<<<<<<<<< conflict 1 of 1 + +++++++++++ zsuskuln 10d994ef <<<<<<< a - ----------- Contents of base + ----------- rlvkpnrz 04dceede ======= base - +++++++++++ Contents of side #2 + +++++++++++ royxmykx 7f215575 >>>>>>> b - >>>>>>>>>>> Conflict 1 of 1 ends + >>>>>>>>>>> conflict 1 of 1 ends "); insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" diff --git a/file b/file --- a/file +++ b/file @@ -1,8 +1,8 @@ - <<<<<<<<<<< Conflict 1 of 1 - +++++++++++ Contents of side #1 + <<<<<<<<<<< conflict 1 of 1 + +++++++++++ zsuskuln 10d994ef -<<<<<<< a +<<<<<<< A - ----------- Contents of base + ----------- rlvkpnrz 04dceede -======= base +======= BASE - +++++++++++ Contents of side #2 + +++++++++++ royxmykx 7f215575 ->>>>>>> b +>>>>>>> B - >>>>>>>>>>> Conflict 1 of 1 ends + >>>>>>>>>>> conflict 1 of 1 ends [EOF] "); insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" @@ -1639,17 +1639,17 @@ fn test_resolve_long_conflict_markers() { "resolve", r#"--config=merge-tools.fake-editor.merge-args=["$output", "$marker_length"]"#, ]); - insta::assert_snapshot!(output, @r###" + insta::assert_snapshot!(output, @r" ------- stderr ------- Resolving conflicts in: file - Working copy (@) now at: vruxwmqv 2cf0bfd3 conflict | (conflict) conflict + Working copy (@) now at: vruxwmqv 49d6c548 conflict | (conflict) conflict Parent commit (@-) : zsuskuln 10d994ef a | a Parent commit (@-) : royxmykx 7f215575 b | b Added 0 files, modified 1 files, removed 0 files Warning: There are unresolved conflicts at these paths: file 2-sided conflict New conflicts appeared in 1 commits: - vruxwmqv 2cf0bfd3 conflict | (conflict) conflict + vruxwmqv 49d6c548 conflict | (conflict) conflict Hint: To resolve the conflicts, start by creating a commit on top of the conflicted commit: jj new vruxwmqv @@ -1657,23 +1657,23 @@ fn test_resolve_long_conflict_markers() { Once the conflicts are resolved, you can inspect the result with `jj diff`. Then run `jj squash` to move the resolution into the conflicted commit. [EOF] - "###); + "); insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" diff --git a/file b/file --- a/file +++ b/file @@ -1,8 +1,8 @@ - <<<<<<<<<<< Conflict 1 of 1 - +++++++++++ Contents of side #1 + <<<<<<<<<<< conflict 1 of 1 + +++++++++++ zsuskuln 10d994ef -<<<<<<< a +<<<<<<< A - ----------- Contents of base + ----------- rlvkpnrz 04dceede -======= base +======= BASE - +++++++++++ Contents of side #2 + +++++++++++ royxmykx 7f215575 ->>>>>>> b +>>>>>>> B - >>>>>>>>>>> Conflict 1 of 1 ends + >>>>>>>>>>> conflict 1 of 1 ends [EOF] "); insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" @@ -1739,22 +1739,22 @@ fn test_multiple_conflicts() { "); insta::assert_snapshot!( work_dir.read_file("this_file_has_a_very_long_name_to_test_padding"), @r" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% zsuskuln 2c821f70 compared with rlvkpnrz fa081b8c -first base +first a - +++++++ Contents of side #2 + +++++++ royxmykx 4c2029de first b - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends "); insta::assert_snapshot!(work_dir.read_file("another_file"), @r" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% zsuskuln 2c821f70 compared with rlvkpnrz fa081b8c -second base +second a - +++++++ Contents of side #2 + +++++++ royxmykx 4c2029de second b - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends "); let setup_opid = work_dir.current_operation_id(); insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" @@ -1772,17 +1772,17 @@ fn test_multiple_conflicts() { // Check that we can manually pick which of the conflicts to resolve first std::fs::write(&editor_script, "expect\n\0write\nresolution another_file\n").unwrap(); let output = work_dir.run_jj(["resolve", "another_file"]); - insta::assert_snapshot!(output, @r###" + insta::assert_snapshot!(output, @r" ------- stderr ------- Resolving conflicts in: another_file - Working copy (@) now at: vruxwmqv d3584f6e conflict | (conflict) conflict + Working copy (@) now at: vruxwmqv f91080aa conflict | (conflict) conflict Parent commit (@-) : zsuskuln 2c821f70 a | a Parent commit (@-) : royxmykx 4c2029de b | b Added 0 files, modified 1 files, removed 0 files Warning: There are unresolved conflicts at these paths: this_file_has_a_very_long_name_to_test_padding 2-sided conflict New conflicts appeared in 1 commits: - vruxwmqv d3584f6e conflict | (conflict) conflict + vruxwmqv f91080aa conflict | (conflict) conflict Hint: To resolve the conflicts, start by creating a commit on top of the conflicted commit: jj new vruxwmqv @@ -1790,20 +1790,20 @@ fn test_multiple_conflicts() { Once the conflicts are resolved, you can inspect the result with `jj diff`. Then run `jj squash` to move the resolution into the conflicted commit. [EOF] - "###); + "); insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" diff --git a/another_file b/another_file index 0000000000..a9fcc7d486 100644 --- a/another_file +++ b/another_file @@ -1,7 +1,1 @@ - -<<<<<<< Conflict 1 of 1 - -%%%%%%% Changes from base to side #1 + -<<<<<<< conflict 1 of 1 + -%%%%%%% zsuskuln 2c821f70 compared with rlvkpnrz fa081b8c --second base -+second a - -+++++++ Contents of side #2 + -+++++++ royxmykx 4c2029de -second b - ->>>>>>> Conflict 1 of 1 ends + ->>>>>>> conflict 1 of 1 ends +resolution another_file [EOF] "); @@ -1840,26 +1840,26 @@ fn test_multiple_conflicts() { --- a/another_file +++ b/another_file @@ -1,7 +1,1 @@ - -<<<<<<< Conflict 1 of 1 - -%%%%%%% Changes from base to side #1 + -<<<<<<< conflict 1 of 1 + -%%%%%%% zsuskuln 2c821f70 compared with rlvkpnrz fa081b8c --second base -+second a - -+++++++ Contents of side #2 + -+++++++ royxmykx 4c2029de -second b - ->>>>>>> Conflict 1 of 1 ends + ->>>>>>> conflict 1 of 1 ends +first resolution for auto-chosen file diff --git a/this_file_has_a_very_long_name_to_test_padding b/this_file_has_a_very_long_name_to_test_padding index 0000000000..f8c72adf17 100644 --- a/this_file_has_a_very_long_name_to_test_padding +++ b/this_file_has_a_very_long_name_to_test_padding @@ -1,7 +1,1 @@ - -<<<<<<< Conflict 1 of 1 - -%%%%%%% Changes from base to side #1 + -<<<<<<< conflict 1 of 1 + -%%%%%%% zsuskuln 2c821f70 compared with rlvkpnrz fa081b8c --first base -+first a - -+++++++ Contents of side #2 + -+++++++ royxmykx 4c2029de -first b - ->>>>>>> Conflict 1 of 1 ends + ->>>>>>> conflict 1 of 1 ends +second resolution for auto-chosen file [EOF] "); @@ -1915,23 +1915,23 @@ fn test_multiple_conflicts_with_error() { [EOF] "); insta::assert_snapshot!(work_dir.read_file("file1"), @r" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% zsuskuln 6c31698c compared with rlvkpnrz 6591ac1d -base1 +a1 - +++++++ Contents of side #2 + +++++++ royxmykx ba0a5538 b1 - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends " ); insta::assert_snapshot!(work_dir.read_file("file2"), @r" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% zsuskuln 6c31698c compared with rlvkpnrz 6591ac1d -base2 +a2 - +++++++ Contents of side #2 + +++++++ royxmykx ba0a5538 b2 - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends " ); let setup_opid = work_dir.current_operation_id(); @@ -1943,18 +1943,18 @@ fn test_multiple_conflicts_with_error() { ) .unwrap(); let output = work_dir.run_jj(["resolve"]); - insta::assert_snapshot!(output.normalize_stderr_exit_status(), @r###" + insta::assert_snapshot!(output.normalize_stderr_exit_status(), @r" ------- stderr ------- Resolving conflicts in: file1 Resolving conflicts in: file2 - Working copy (@) now at: vruxwmqv 98296abe conflict | (conflict) conflict + Working copy (@) now at: vruxwmqv dac1b0fe conflict | (conflict) conflict Parent commit (@-) : zsuskuln 6c31698c a | a Parent commit (@-) : royxmykx ba0a5538 b | b Added 0 files, modified 1 files, removed 0 files Warning: There are unresolved conflicts at these paths: file2 2-sided conflict New conflicts appeared in 1 commits: - vruxwmqv 98296abe conflict | (conflict) conflict + vruxwmqv dac1b0fe conflict | (conflict) conflict Hint: To resolve the conflicts, start by creating a commit on top of the conflicted commit: jj new vruxwmqv @@ -1965,20 +1965,20 @@ fn test_multiple_conflicts_with_error() { Caused by: The output file is either unchanged or empty after the editor quit (run with --debug to see the exact invocation). [EOF] [exit status: 1] - "###); + "); insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" diff --git a/file1 b/file1 index 0000000000..95cc18629d 100644 --- a/file1 +++ b/file1 @@ -1,7 +1,1 @@ - -<<<<<<< Conflict 1 of 1 - -%%%%%%% Changes from base to side #1 + -<<<<<<< conflict 1 of 1 + -%%%%%%% zsuskuln 6c31698c compared with rlvkpnrz 6591ac1d --base1 -+a1 - -+++++++ Contents of side #2 + -+++++++ royxmykx ba0a5538 -b1 - ->>>>>>> Conflict 1 of 1 ends + ->>>>>>> conflict 1 of 1 ends +resolution1 [EOF] "); @@ -1995,18 +1995,18 @@ fn test_multiple_conflicts_with_error() { ) .unwrap(); let output = work_dir.run_jj(["resolve"]); - insta::assert_snapshot!(output.normalize_stderr_exit_status(), @r###" + insta::assert_snapshot!(output.normalize_stderr_exit_status(), @r" ------- stderr ------- Resolving conflicts in: file1 Resolving conflicts in: file2 - Working copy (@) now at: vruxwmqv 7daa6406 conflict | (conflict) conflict + Working copy (@) now at: vruxwmqv 4a7cc123 conflict | (conflict) conflict Parent commit (@-) : zsuskuln 6c31698c a | a Parent commit (@-) : royxmykx ba0a5538 b | b Added 0 files, modified 1 files, removed 0 files Warning: There are unresolved conflicts at these paths: file2 2-sided conflict New conflicts appeared in 1 commits: - vruxwmqv 7daa6406 conflict | (conflict) conflict + vruxwmqv 4a7cc123 conflict | (conflict) conflict Hint: To resolve the conflicts, start by creating a commit on top of the conflicted commit: jj new vruxwmqv @@ -2017,20 +2017,20 @@ fn test_multiple_conflicts_with_error() { Caused by: Tool exited with exit status: 1 (run with --debug to see the exact invocation) [EOF] [exit status: 1] - "###); + "); insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" diff --git a/file1 b/file1 index 0000000000..95cc18629d 100644 --- a/file1 +++ b/file1 @@ -1,7 +1,1 @@ - -<<<<<<< Conflict 1 of 1 - -%%%%%%% Changes from base to side #1 + -<<<<<<< conflict 1 of 1 + -%%%%%%% zsuskuln 6c31698c compared with rlvkpnrz 6591ac1d --base1 -+a1 - -+++++++ Contents of side #2 + -+++++++ royxmykx ba0a5538 -b1 - ->>>>>>> Conflict 1 of 1 ends + ->>>>>>> conflict 1 of 1 ends +resolution1 [EOF] "); @@ -2109,22 +2109,22 @@ fn test_resolve_with_contents_of_side() { [EOF] "); insta::assert_snapshot!(work_dir.read_file("file"), @r" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% zsuskuln 72dced6e compared with rlvkpnrz ed3e06b2 -base +a - +++++++ Contents of side #2 + +++++++ vruxwmqv dd35236a b - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends "); insta::assert_snapshot!(work_dir.read_file("other"), @r" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% royxmykx e5747f42 compared with rlvkpnrz ed3e06b2 -base +left - +++++++ Contents of side #2 + +++++++ vruxwmqv dd35236a right - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends "); let setup_opid = work_dir.current_operation_id(); diff --git a/cli/tests/test_restore_command.rs b/cli/tests/test_restore_command.rs index a2779927946..13ccc9ec744 100644 --- a/cli/tests/test_restore_command.rs +++ b/cli/tests/test_restore_command.rs @@ -67,13 +67,13 @@ fn test_restore() { insta::assert_snapshot!(output, @r" ------- stderr ------- Rebased 1 descendant commits - Working copy (@) now at: kkmpptxz 4f7af0b0 (conflict) (no description set) + Working copy (@) now at: kkmpptxz 22f6441a (conflict) (no description set) Parent commit (@-) : rlvkpnrz 67841e01 (empty) (no description set) Added 0 files, modified 1 files, removed 0 files Warning: There are unresolved conflicts at these paths: file2 2-sided conflict including 1 deletion New conflicts appeared in 1 commits: - kkmpptxz 4f7af0b0 (conflict) (no description set) + kkmpptxz 22f6441a (conflict) (no description set) Hint: To resolve the conflicts, start by creating a commit on top of the conflicted commit: jj new kkmpptxz @@ -181,26 +181,26 @@ fn test_restore_conflicted_merge() { [EOF] "); insta::assert_snapshot!(work_dir.read_file("file"), @r" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% zsuskuln 45537d53 compared with rlvkpnrz 1792382a -base +a - +++++++ Contents of side #2 + +++++++ royxmykx 89d1b299 b - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends "); // Overwrite the file... work_dir.write_file("file", "resolution"); insta::assert_snapshot!(work_dir.run_jj(["diff"]), @r" Resolved conflict in file: - 1 : <<<<<<< Conflict 1 of 1 - 2 : %%%%%%% Changes from base to side #1 + 1 : <<<<<<< conflict 1 of 1 + 2 : %%%%%%% zsuskuln 45537d53 compared with rlvkpnrz 1792382a 3 : -base 4 : +a - 5 : +++++++ Contents of side #2 + 5 : +++++++ royxmykx 89d1b299 6 : b - 7 : >>>>>>> Conflict 1 of 1 ends + 7 : >>>>>>> conflict 1 of 1 ends 1: resolution [EOF] "); @@ -218,13 +218,13 @@ fn test_restore_conflicted_merge() { [EOF] "); insta::assert_snapshot!(work_dir.read_file("file"), @r" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% side #1 compared with base -base +a - +++++++ Contents of side #2 + +++++++ side #2 b - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends "); let output = work_dir.run_jj(["diff"]); insta::assert_snapshot!(output, @""); @@ -233,13 +233,13 @@ fn test_restore_conflicted_merge() { work_dir.write_file("file", "resolution"); insta::assert_snapshot!(work_dir.run_jj(["diff"]), @r" Resolved conflict in file: - 1 : <<<<<<< Conflict 1 of 1 - 2 : %%%%%%% Changes from base to side #1 + 1 : <<<<<<< conflict 1 of 1 + 2 : %%%%%%% zsuskuln 45537d53 compared with rlvkpnrz 1792382a 3 : -base 4 : +a - 5 : +++++++ Contents of side #2 + 5 : +++++++ royxmykx 89d1b299 6 : b - 7 : >>>>>>> Conflict 1 of 1 ends + 7 : >>>>>>> conflict 1 of 1 ends 1: resolution [EOF] "); @@ -248,7 +248,7 @@ fn test_restore_conflicted_merge() { let output = work_dir.run_jj(["restore"]); insta::assert_snapshot!(output, @r" ------- stderr ------- - Working copy (@) now at: vruxwmqv 846bb35c conflict | (conflict) (empty) conflict + Working copy (@) now at: vruxwmqv 5d89824a conflict | (conflict) (empty) conflict Parent commit (@-) : zsuskuln 45537d53 a | a Parent commit (@-) : royxmykx 89d1b299 b | b Added 0 files, modified 1 files, removed 0 files @@ -257,13 +257,13 @@ fn test_restore_conflicted_merge() { [EOF] "); insta::assert_snapshot!(work_dir.read_file("file"), @r" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% zsuskuln 45537d53 compared with rlvkpnrz 1792382a -base +a - +++++++ Contents of side #2 + +++++++ royxmykx 89d1b299 b - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends "); } diff --git a/cli/tests/test_squash_command.rs b/cli/tests/test_squash_command.rs index 17aaefa1101..9458ac8d366 100644 --- a/cli/tests/test_squash_command.rs +++ b/cli/tests/test_squash_command.rs @@ -869,13 +869,13 @@ fn test_squash_from_multiple() { // Squash a few commits sideways let output = work_dir.run_jj(["squash", "--from=b", "--from=c", "--into=d"]); - insta::assert_snapshot!(output, @r###" + insta::assert_snapshot!(output, @r" ------- stderr ------- Rebased 2 descendant commits - Working copy (@) now at: kpqxywon 703c6f0c f | (no description set) - Parent commit (@-) : yostqsxw 3d6a1899 e | (no description set) + Working copy (@) now at: kpqxywon 49727075 f | (no description set) + Parent commit (@-) : yostqsxw 2e4142e6 e | (no description set) New conflicts appeared in 1 commits: - yqosqzyt a3221d7a d | (conflict) (no description set) + yqosqzyt 5ae324ae d | (conflict) (no description set) Hint: To resolve the conflicts, start by creating a commit on top of the conflicted commit: jj new yqosqzyt @@ -883,12 +883,12 @@ fn test_squash_from_multiple() { Once the conflicts are resolved, you can inspect the result with `jj diff`. Then run `jj squash` to move the resolution into the conflicted commit. [EOF] - "###); + "); insta::assert_snapshot!(get_log_output(&work_dir), @r" - @ 703c6f0cae6f f - ○ 3d6a18995cae e + @ 49727075f875 f + ○ 2e4142e67bae e ├─╮ - × │ a3221d7ae02a d + × │ 5ae324aed855 d ├─╯ ○ e88768e65e67 a b c ◆ 000000000000 (empty) @@ -897,16 +897,16 @@ fn test_squash_from_multiple() { // The changes from the sources have been applied let output = work_dir.run_jj(["file", "show", "-r=d", "file"]); insta::assert_snapshot!(output, @r" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base #1 to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% squash destination (yqosqzyt 8acbb715) compared with parents of kkmpptxz fed4d1a2 -a +d - %%%%%%% Changes from base #2 to side #2 + %%%%%%% squashed commit (kkmpptxz fed4d1a2) compared with parents of mzvwutvl d7e94ec7 -a +b - +++++++ Contents of side #3 + +++++++ squashed commit (mzvwutvl d7e94ec7) c - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends [EOF] "); @@ -1013,13 +1013,13 @@ fn test_squash_from_multiple_partial() { // Partially squash a few commits sideways let output = work_dir.run_jj(["squash", "--from=b|c", "--into=d", "file1"]); - insta::assert_snapshot!(output, @r###" + insta::assert_snapshot!(output, @r" ------- stderr ------- Rebased 2 descendant commits - Working copy (@) now at: kpqxywon f3ae0274 f | (no description set) - Parent commit (@-) : yostqsxw 45ad30bd e | (no description set) + Working copy (@) now at: kpqxywon de844869 f | (no description set) + Parent commit (@-) : yostqsxw c6dd948e e | (no description set) New conflicts appeared in 1 commits: - yqosqzyt 15efa8c0 d | (conflict) (no description set) + yqosqzyt 95cc1bb7 d | (conflict) (no description set) Hint: To resolve the conflicts, start by creating a commit on top of the conflicted commit: jj new yqosqzyt @@ -1027,15 +1027,15 @@ fn test_squash_from_multiple_partial() { Once the conflicts are resolved, you can inspect the result with `jj diff`. Then run `jj squash` to move the resolution into the conflicted commit. [EOF] - "###); + "); insta::assert_snapshot!(get_log_output(&work_dir), @r" - @ f3ae0274fb6c f - ○ 45ad30bdccc6 e + @ de8448696599 f + ○ c6dd948e8e55 e ├─┬─╮ │ │ ○ e9db15b956c4 b │ ○ │ 83cbe51db94d c │ ├─╯ - × │ 15efa8c069e0 d + × │ 95cc1bb77ab6 d ├─╯ ○ 64ea60be8d77 a ◆ 000000000000 (empty) @@ -1055,16 +1055,16 @@ fn test_squash_from_multiple_partial() { // The selected changes from the sources have been applied let output = work_dir.run_jj(["file", "show", "-r=d", "file1"]); insta::assert_snapshot!(output, @r" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base #1 to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% squash destination (yqosqzyt f6812ff8) compared with parents of kkmpptxz f2c9709f -a +d - %%%%%%% Changes from base #2 to side #2 + %%%%%%% squashed commit (selected changes from kkmpptxz f2c9709f) compared with parents of mzvwutvl aa908686 -a +b - +++++++ Contents of side #3 + +++++++ squashed commit (selected changes from mzvwutvl aa908686) c - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends [EOF] "); // The unselected change from the sources have not been applied to the diff --git a/cli/tests/test_status_command.rs b/cli/tests/test_status_command.rs index 53ba0b4f60e..6cf193e45d3 100644 --- a/cli/tests/test_status_command.rs +++ b/cli/tests/test_status_command.rs @@ -230,11 +230,11 @@ fn test_status_display_relevant_working_commit_conflict_hints() { let output = work_dir.run_jj(["log", "-r", "::"]); insta::assert_snapshot!(output, @r" - @ yqosqzyt test.user@example.com 2001-02-03 08:05:13 7e0bc4cf conflict + @ yqosqzyt test.user@example.com 2001-02-03 08:05:13 61bf5b5b conflict │ (empty) boom-cont-2 - × royxmykx test.user@example.com 2001-02-03 08:05:12 681c71af conflict + × royxmykx test.user@example.com 2001-02-03 08:05:12 249310ec conflict │ (empty) boom-cont - × mzvwutvl test.user@example.com 2001-02-03 08:05:11 30558616 conflict + × mzvwutvl test.user@example.com 2001-02-03 08:05:11 61e78c73 conflict ├─╮ (empty) boom │ ○ kkmpptxz test.user@example.com 2001-02-03 08:05:10 bb11a679 │ │ First part of conflicting change @@ -247,10 +247,10 @@ fn test_status_display_relevant_working_commit_conflict_hints() { "); let output = work_dir.run_jj(["status"]); - insta::assert_snapshot!(output, @r###" + insta::assert_snapshot!(output, @r" The working copy has no changes. - Working copy (@) : yqosqzyt 7e0bc4cf (conflict) (empty) boom-cont-2 - Parent commit (@-): royxmykx 681c71af (conflict) (empty) boom-cont + Working copy (@) : yqosqzyt 61bf5b5b (conflict) (empty) boom-cont-2 + Parent commit (@-): royxmykx 249310ec (conflict) (empty) boom-cont Warning: There are unresolved conflicts at these paths: conflicted.txt 2-sided conflict Hint: To resolve the conflicts, start by creating a commit on top of @@ -260,13 +260,13 @@ fn test_status_display_relevant_working_commit_conflict_hints() { Once the conflicts are resolved, you can inspect the result with `jj diff`. Then run `jj squash` to move the resolution into the conflicted commit. [EOF] - "###); + "); let output = work_dir.run_jj(["status", "--color=always"]); - insta::assert_snapshot!(output, @r###" + insta::assert_snapshot!(output, @r" The working copy has no changes. - Working copy (@) : yqosqzyt 7e0bc4cf (conflict) (empty) boom-cont-2 - Parent commit (@-): royxmykx 681c71af (conflict) (empty) boom-cont + Working copy (@) : yqosqzyt 61bf5b5b (conflict) (empty) boom-cont-2 + Parent commit (@-): royxmykx 249310ec (conflict) (empty) boom-cont Warning: There are unresolved conflicts at these paths: conflicted.txt 2-sided conflict Hint: To resolve the conflicts, start by creating a commit on top of @@ -276,13 +276,13 @@ fn test_status_display_relevant_working_commit_conflict_hints() { Once the conflicts are resolved, you can inspect the result with `jj diff`. Then run `jj squash` to move the resolution into the conflicted commit. [EOF] - "###); + "); let output = work_dir.run_jj(["status", "--config=hints.resolving-conflicts=false"]); insta::assert_snapshot!(output, @r" The working copy has no changes. - Working copy (@) : yqosqzyt 7e0bc4cf (conflict) (empty) boom-cont-2 - Parent commit (@-): royxmykx 681c71af (conflict) (empty) boom-cont + Working copy (@) : yqosqzyt 61bf5b5b (conflict) (empty) boom-cont-2 + Parent commit (@-): royxmykx 249310ec (conflict) (empty) boom-cont Warning: There are unresolved conflicts at these paths: conflicted.txt 2-sided conflict [EOF] @@ -300,15 +300,15 @@ fn test_status_display_relevant_working_commit_conflict_hints() { let output = work_dir.run_jj(["log", "-r", "::"]); insta::assert_snapshot!(output, @r" - @ wqnwkozp test.user@example.com 2001-02-03 08:05:20 cc7d68f7 + @ wqnwkozp test.user@example.com 2001-02-03 08:05:20 66b67bf6 │ fixed 2 - ○ kmkuslsw test.user@example.com 2001-02-03 08:05:19 812e2163 + ○ kmkuslsw test.user@example.com 2001-02-03 08:05:19 f49a1415 │ fixed 1 - × yqosqzyt test.user@example.com 2001-02-03 08:05:13 7e0bc4cf conflict + × yqosqzyt test.user@example.com 2001-02-03 08:05:13 61bf5b5b conflict │ (empty) boom-cont-2 - × royxmykx test.user@example.com 2001-02-03 08:05:12 681c71af conflict + × royxmykx test.user@example.com 2001-02-03 08:05:12 249310ec conflict │ (empty) boom-cont - × mzvwutvl test.user@example.com 2001-02-03 08:05:11 30558616 conflict + × mzvwutvl test.user@example.com 2001-02-03 08:05:11 61e78c73 conflict ├─╮ (empty) boom │ ○ kkmpptxz test.user@example.com 2001-02-03 08:05:10 bb11a679 │ │ First part of conflicting change @@ -325,8 +325,8 @@ fn test_status_display_relevant_working_commit_conflict_hints() { insta::assert_snapshot!(output, @r" Working copy changes: M conflicted.txt - Working copy (@) : wqnwkozp cc7d68f7 fixed 2 - Parent commit (@-): kmkuslsw 812e2163 fixed 1 + Working copy (@) : wqnwkozp 66b67bf6 fixed 2 + Parent commit (@-): kmkuslsw f49a1415 fixed 1 [EOF] "); @@ -336,15 +336,15 @@ fn test_status_display_relevant_working_commit_conflict_hints() { let output = work_dir.run_jj(["log", "-r", "::"]); insta::assert_snapshot!(output, @r" - ○ wqnwkozp test.user@example.com 2001-02-03 08:05:20 cc7d68f7 + ○ wqnwkozp test.user@example.com 2001-02-03 08:05:20 66b67bf6 │ fixed 2 - @ kmkuslsw test.user@example.com 2001-02-03 08:05:19 812e2163 + @ kmkuslsw test.user@example.com 2001-02-03 08:05:19 f49a1415 │ fixed 1 - × yqosqzyt test.user@example.com 2001-02-03 08:05:13 7e0bc4cf conflict + × yqosqzyt test.user@example.com 2001-02-03 08:05:13 61bf5b5b conflict │ (empty) boom-cont-2 - × royxmykx test.user@example.com 2001-02-03 08:05:12 681c71af conflict + × royxmykx test.user@example.com 2001-02-03 08:05:12 249310ec conflict │ (empty) boom-cont - × mzvwutvl test.user@example.com 2001-02-03 08:05:11 30558616 conflict + × mzvwutvl test.user@example.com 2001-02-03 08:05:11 61e78c73 conflict ├─╮ (empty) boom │ ○ kkmpptxz test.user@example.com 2001-02-03 08:05:10 bb11a679 │ │ First part of conflicting change @@ -361,8 +361,8 @@ fn test_status_display_relevant_working_commit_conflict_hints() { insta::assert_snapshot!(output, @r" Working copy changes: M conflicted.txt - Working copy (@) : kmkuslsw 812e2163 fixed 1 - Parent commit (@-): yqosqzyt 7e0bc4cf (conflict) (empty) boom-cont-2 + Working copy (@) : kmkuslsw f49a1415 fixed 1 + Parent commit (@-): yqosqzyt 61bf5b5b (conflict) (empty) boom-cont-2 Hint: Conflict in parent commit has been resolved in working copy [EOF] "); @@ -374,15 +374,15 @@ fn test_status_display_relevant_working_commit_conflict_hints() { let output = work_dir.run_jj(["log", "-r", "::"]); insta::assert_snapshot!(output, @r" - ○ wqnwkozp test.user@example.com 2001-02-03 08:05:20 cc7d68f7 + ○ wqnwkozp test.user@example.com 2001-02-03 08:05:20 66b67bf6 │ fixed 2 - ○ kmkuslsw test.user@example.com 2001-02-03 08:05:19 812e2163 + ○ kmkuslsw test.user@example.com 2001-02-03 08:05:19 f49a1415 │ fixed 1 - × yqosqzyt test.user@example.com 2001-02-03 08:05:13 7e0bc4cf conflict + × yqosqzyt test.user@example.com 2001-02-03 08:05:13 61bf5b5b conflict │ (empty) boom-cont-2 - × royxmykx test.user@example.com 2001-02-03 08:05:12 681c71af conflict + × royxmykx test.user@example.com 2001-02-03 08:05:12 249310ec conflict │ (empty) boom-cont - × mzvwutvl test.user@example.com 2001-02-03 08:05:11 30558616 conflict + × mzvwutvl test.user@example.com 2001-02-03 08:05:11 61e78c73 conflict ├─╮ (empty) boom │ ○ kkmpptxz test.user@example.com 2001-02-03 08:05:10 bb11a679 │ │ First part of conflicting change @@ -429,11 +429,11 @@ fn test_status_simplify_conflict_sides() { create_commit_with_files(&work_dir, "conflict", &["conflictA", "conflictB"], &[]); insta::assert_snapshot!(work_dir.run_jj(["status"]), - @r###" + @r" The working copy has no changes. - Working copy (@) : nkmrtpmo a5a545ce conflict | (conflict) (empty) conflict - Parent commit (@-): kmkuslsw ccb05364 conflictA | (conflict) (empty) conflictA - Parent commit (@-): lylxulpl d9bc60cb conflictB | (conflict) (empty) conflictB + Working copy (@) : nkmrtpmo ad982224 conflict | (conflict) (empty) conflict + Parent commit (@-): kmkuslsw 31088e28 conflictA | (conflict) (empty) conflictA + Parent commit (@-): lylxulpl 0b4c6dc6 conflictB | (conflict) (empty) conflictB Warning: There are unresolved conflicts at these paths: fileA 2-sided conflict fileB 2-sided conflict @@ -445,7 +445,7 @@ fn test_status_simplify_conflict_sides() { Once the conflicts are resolved, you can inspect the result with `jj diff`. Then run `jj squash` to move the resolution into the conflicted commit. [EOF] - "###); + "); } #[test] diff --git a/cli/tests/test_working_copy.rs b/cli/tests/test_working_copy.rs index 90d78fb06e4..7d2faf93600 100644 --- a/cli/tests/test_working_copy.rs +++ b/cli/tests/test_working_copy.rs @@ -208,16 +208,16 @@ fn test_materialize_and_snapshot_different_conflict_markers() { // File should have Git-style conflict markers insta::assert_snapshot!(work_dir.read_file("file"), @r" line 1 - <<<<<<< Side #1 (Conflict 1 of 1) + <<<<<<< rlvkpnrz df1cdd77 (conflict 1 of 1) line 2 - a line 3 - ||||||| Base + ||||||| qpvuntsm 2205b3ac line 2 line 3 ======= line 2 - b line 3 - b - >>>>>>> Side #2 (Conflict 1 of 1 ends) + >>>>>>> zsuskuln 68dcce1b (conflict 1 of 1 ends) "); // Configure to use JJ-style "snapshot" conflict markers @@ -247,12 +247,12 @@ fn test_materialize_and_snapshot_different_conflict_markers() { --- a/file +++ b/file @@ -2,7 +2,7 @@ - <<<<<<< Conflict 1 of 1 - +++++++ Contents of side #1 + <<<<<<< conflict 1 of 1 + +++++++ rlvkpnrz df1cdd77 line 2 - a -line 3 +line 3 - a - ------- Contents of base + ------- qpvuntsm 2205b3ac line 2 line 3 [EOF] @@ -333,18 +333,18 @@ fn test_conflict_marker_length_stored_in_working_copy() { // File should be materialized with long conflict markers insta::assert_snapshot!(work_dir.read_file("file"), @r" line 1 - <<<<<<<<<<< Conflict 1 of 1 - %%%%%%%%%%% Changes from base to side #1 + <<<<<<<<<<< conflict 1 of 1 + %%%%%%%%%%% rlvkpnrz ccf9527c compared with qpvuntsm 2205b3ac -line 2 -line 3 +line 2 - left +line 3 - left - +++++++++++ Contents of side #2 + +++++++++++ zsuskuln d7acaf48 ======= fake marker line 2 - right ======= fake marker line 3 - >>>>>>>>>>> Conflict 1 of 1 ends + >>>>>>>>>>> conflict 1 of 1 ends "); // The timestamps in the `jj debug local-working-copy` output change, so we want @@ -363,9 +363,9 @@ fn test_conflict_marker_length_stored_in_working_copy() { // Working copy should contain conflict marker length let output = work_dir.run_jj(["debug", "local-working-copy"]); insta::assert_snapshot!(output.normalize_stdout_with(redact_output), @r#" - Current operation: OperationId("da3b34243efe5ea04830cd2211b5be79444fbc2ef23681361fd2f551ebb86772bff21695da95b72388306e028bf04c6d76db10bf4cbd3a08eb34bf744c8900c7") - Current tree: MergedTreeId { tree_ids: Conflicted([TreeId("381273b50cf73f8c81b3f1502ee89e9bbd6c1518"), TreeId("771f3d31c4588ea40a8864b2a981749888e596c2"), TreeId("f56b8223da0dab22b03b8323ced4946329aeb4e0")]) } - Normal { } 249 Some(MaterializedConflictData { conflict_marker_len: 11 }) "file" + Current operation: OperationId("ea5418915502ef18184611383cf95249c2c7029fc9831a45f2db0ddb6a84484af74da8d689163324d04c8b1acf1c6e722c587308a64c3f98fb02b8786fc75ba7") + Current tree: MergedTreeId { tree_ids: Conflicted([TreeId("381273b50cf73f8c81b3f1502ee89e9bbd6c1518"), TreeId("771f3d31c4588ea40a8864b2a981749888e596c2"), TreeId("f56b8223da0dab22b03b8323ced4946329aeb4e0")]), labels: Labeled(["rlvkpnrz ccf9527c", "qpvuntsm 2205b3ac", "zsuskuln d7acaf48"]) } + Normal { } 268 Some(MaterializedConflictData { conflict_marker_len: 11 }) "file" [EOF] "#); @@ -375,20 +375,20 @@ fn test_conflict_marker_length_stored_in_working_copy() { "file", indoc! {" line 1 - <<<<<<<<<<< Conflict 1 of 1 - %%%%%%%%%%% Changes from base to side #1 + <<<<<<<<<<< conflict 1 of 1 + %%%%%%%%%%% side #1 compared with base -line 2 -line 3 +line 2 - left +line 3 - left - +++++++++++ Contents of side #2 + +++++++++++ side #2 <<<<<<< fake marker ||||||| fake marker line 2 - right ======= fake marker line 3 >>>>>>> fake marker - >>>>>>>>>>> Conflict 1 of 1 ends + >>>>>>>>>>> conflict 1 of 1 ends "}, ); @@ -397,7 +397,7 @@ fn test_conflict_marker_length_stored_in_working_copy() { insta::assert_snapshot!(output, @r" Working copy changes: M file - Working copy (@) : mzvwutvl b6b012dc (conflict) (no description set) + Working copy (@) : mzvwutvl 3230b510 (conflict) (no description set) Parent commit (@-): rlvkpnrz ccf9527c side-a Parent commit (@-): zsuskuln d7acaf48 side-b Warning: There are unresolved conflicts at these paths: @@ -411,7 +411,7 @@ fn test_conflict_marker_length_stored_in_working_copy() { @@ -6,8 +6,10 @@ +line 2 - left +line 3 - left - +++++++++++ Contents of side #2 + +++++++++++ zsuskuln d7acaf48 -======= fake marker +<<<<<<< fake marker +||||||| fake marker @@ -419,16 +419,16 @@ fn test_conflict_marker_length_stored_in_working_copy() { ======= fake marker line 3 +>>>>>>> fake marker - >>>>>>>>>>> Conflict 1 of 1 ends + >>>>>>>>>>> conflict 1 of 1 ends [EOF] "); // Working copy should still contain conflict marker length let output = work_dir.run_jj(["debug", "local-working-copy"]); insta::assert_snapshot!(output.normalize_stdout_with(redact_output), @r#" - Current operation: OperationId("3de33bbfe3a9df8a052cc243aeedac6a3240d6115cb88f2779a1b6f1289288c6e78153875e48e41c17c098418f681bc872c54743e76b9e210f08533c50fc5a26") - Current tree: MergedTreeId { tree_ids: Conflicted([TreeId("381273b50cf73f8c81b3f1502ee89e9bbd6c1518"), TreeId("771f3d31c4588ea40a8864b2a981749888e596c2"), TreeId("3329c18c95f7b7a55c278c2259e9c4ce711fae59")]) } - Normal { } 289 Some(MaterializedConflictData { conflict_marker_len: 11 }) "file" + Current operation: OperationId("79bae559a4fe31043a789a569c9327ded49b32872ed03561449bea04f220b812c6655e1d23f7b989fd24685fbdac37813d46d9d427f43e7baf6e974c1145c244") + Current tree: MergedTreeId { tree_ids: Conflicted([TreeId("381273b50cf73f8c81b3f1502ee89e9bbd6c1518"), TreeId("771f3d31c4588ea40a8864b2a981749888e596c2"), TreeId("3329c18c95f7b7a55c278c2259e9c4ce711fae59")]), labels: Labeled(["rlvkpnrz ccf9527c", "qpvuntsm 2205b3ac", "zsuskuln d7acaf48"]) } + Normal { } 275 Some(MaterializedConflictData { conflict_marker_len: 11 }) "file" [EOF] "#); @@ -461,8 +461,8 @@ fn test_conflict_marker_length_stored_in_working_copy() { // working copy let output = work_dir.run_jj(["debug", "local-working-copy"]); insta::assert_snapshot!(output.normalize_stdout_with(redact_output), @r#" - Current operation: OperationId("2676b66a8d17cf7913d2260285abe6f3ca4c8dc8f3fdfb3f54a4d566c9199670f80123a7174b553ff67c13c20c6827cde2429847a7949c19bc52f2397139e4c9") - Current tree: MergedTreeId { tree_ids: Resolved(TreeId("6120567b3cb2472d549753ed3e4b84183d52a650")) } + Current operation: OperationId("2522cbd3a6a115a4860facfdfd97d0ee7b1d72704006a86e4d10988c030ddc2f311a262cf0683b7c124fa7710d7126aac688bd788c417352b57dd9d1ae6f9650") + Current tree: MergedTreeId { tree_ids: Resolved(TreeId("6120567b3cb2472d549753ed3e4b84183d52a650")), labels: Unlabeled } Normal { } 130 None "file" [EOF] "#); diff --git a/cli/tests/test_workspaces.rs b/cli/tests/test_workspaces.rs index 0d9f5bd8c55..50dce8ae74d 100644 --- a/cli/tests/test_workspaces.rs +++ b/cli/tests/test_workspaces.rs @@ -561,7 +561,7 @@ fn test_workspaces_conflicting_edits() { insta::assert_snapshot!(get_log_output(&secondary_dir), @r" @ 90f3d42e0bff secondary@ (divergent) - │ × de7155dbea42 (divergent) + │ × 921cefdab658 (divergent) ├─╯ │ ○ 3a9b690d6e67 default@ ├─╯ @@ -572,7 +572,7 @@ fn test_workspaces_conflicting_edits() { // The stale working copy should have been resolved by the previous command insta::assert_snapshot!(get_log_output(&secondary_dir), @r" @ 90f3d42e0bff secondary@ (divergent) - │ × de7155dbea42 (divergent) + │ × 921cefdab658 (divergent) ├─╯ │ ○ 3a9b690d6e67 default@ ├─╯ diff --git a/docs/conflicts.md b/docs/conflicts.md index 265c7ec041e..ad32ab49561 100644 --- a/docs/conflicts.md +++ b/docs/conflicts.md @@ -79,17 +79,17 @@ will materialize the conflict in the working copy using conflict markers, which would look like this: ```text -<<<<<<< Conflict 1 of 1 -%%%%%%% Changes from base to side #1 +<<<<<<< conflict 1 of 1 +%%%%%%% side #1 compared with base apple -grape +grapefruit orange -+++++++ Contents of side #2 ++++++++ side #2 APPLE GRAPE ORANGE ->>>>>>> Conflict 1 of 1 ends +>>>>>>> conflict 1 of 1 ends ``` The markers `<<<<<<<` and `>>>>>>>` indicate the start and end of a conflict @@ -124,20 +124,20 @@ diff, Jujutsu also supports a "snapshot" style, which can be enabled by setting the `ui.conflict-marker-style` config option to "snapshot": ```text -<<<<<<< Conflict 1 of 1 -+++++++ Contents of side #1 +<<<<<<< conflict 1 of 1 ++++++++ side #1 apple grapefruit orange -------- Contents of base +------- base apple grape orange -+++++++ Contents of side #2 ++++++++ side #2 APPLE GRAPE ORANGE ->>>>>>> Conflict 1 of 1 ends +>>>>>>> conflict 1 of 1 ends ``` Some tools expect Git-style conflict markers, so Jujutsu also supports [Git's @@ -146,11 +146,11 @@ conflict markers by setting the `ui.conflict-marker-style` config option to "git": ```text -<<<<<<< Side #1 (Conflict 1 of 1) +<<<<<<< side #1 (conflict 1 of 1) apple grapefruit orange -||||||| Base +||||||| base apple grape orange @@ -158,7 +158,7 @@ orange APPLE GRAPE ORANGE ->>>>>>> Side #2 (Conflict 1 of 1 ends) +>>>>>>> side #2 (conflict 1 of 1 ends) ``` This conflict marker style only supports 2-sided conflicts though, so it falls @@ -174,15 +174,15 @@ markers and which are just part of the file contents, `jj` sometimes uses conflict markers which are longer than normal: ```text -<<<<<<<<<<<<<<< Conflict 1 of 1 -%%%%%%%%%%%%%%% Changes from base to side #1 +<<<<<<<<<<<<<<< conflict 1 of 1 +%%%%%%%%%%%%%%% side #1 compared with base -Heading +HEADING ======= -+++++++++++++++ Contents of side #2 ++++++++++++++++ side #2 New Heading =========== ->>>>>>>>>>>>>>> Conflict 1 of 1 ends +>>>>>>>>>>>>>>> conflict 1 of 1 ends ``` ## Conflicts with missing terminating newline @@ -204,13 +204,13 @@ added the missing newline character to make `grape\n`, the resulting conflict would look like this: ```text -<<<<<<< Conflict 1 of 1 -+++++++ Contents of side #1 (no terminating newline) +<<<<<<< conflict 1 of 1 ++++++++ side #1 (no terminating newline) grapefruit -%%%%%%% Changes from base to side #2 (adds terminating newline) +%%%%%%% side #2 compared with base (adds terminating newline) -grape +grape ->>>>>>> Conflict 1 of 1 ends +>>>>>>> conflict 1 of 1 ends ``` Therefore, a resolution of this conflict could be `grapefruit\n`, with the diff --git a/docs/tutorial.md b/docs/tutorial.md index 89e06992b22..eb68b86393f 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -337,13 +337,13 @@ Once the conflicts are resolved, you can inspect the result with `jj diff`. Then run `jj squash` to move the resolution into the conflicted commit. $ cat file1 -<<<<<<< Conflict 1 of 1 -%%%%%%% Changes from base to side #1 +<<<<<<< conflict 1 of 1 +%%%%%%% rebase destination (nuvyytnq 5dda2f09) compared with ovknlmro 7d7c6e6b -b1 +a -+++++++ Contents of side #2 ++++++++ rebased commit (puqltutt daa6ffd5) b2 ->>>>>>> Conflict 1 of 1 ends +>>>>>>> conflict 1 of 1 ends $ echo resolved > file1 diff --git a/lib/src/absorb.rs b/lib/src/absorb.rs index 39f69f6cf4a..644d24b47c9 100644 --- a/lib/src/absorb.rs +++ b/lib/src/absorb.rs @@ -39,6 +39,7 @@ use crate::copies::CopyRecords; use crate::diff::ContentDiff; use crate::diff::DiffHunkKind; use crate::matchers::Matcher; +use crate::merge::Diff; use crate::merge::Merge; use crate::merged_tree::MergedTree; use crate::merged_tree::MergedTreeBuilder; @@ -102,7 +103,11 @@ pub async fn split_hunks_to_trees( // TODO: enable copy tracking if we add support for annotate and merge let copy_records = CopyRecords::default(); let tree_diff = left_tree.diff_stream_with_copies(&right_tree, matcher, ©_records); - let mut diff_stream = materialized_diff_stream(repo.store(), tree_diff); + let mut diff_stream = materialized_diff_stream( + repo.store(), + tree_diff, + Diff::new(left_tree.labels(), right_tree.labels()), + ); while let Some(entry) = diff_stream.next().await { let left_path = entry.path.source(); let right_path = entry.path.target(); @@ -319,7 +324,7 @@ pub fn absorb_hunks( let destination_tree = store.get_root_tree(commit_builder.tree_id())?; let selected_tree = store.get_root_tree(&selected_tree_id)?; let new_tree = destination_tree - .merge(source.parent_tree.clone(), selected_tree) + .merge_unlabeled(source.parent_tree.clone(), selected_tree) .block_on()?; let mut predecessors = commit_builder.predecessors().to_vec(); predecessors.push(source.commit.id().clone()); diff --git a/lib/src/annotate.rs b/lib/src/annotate.rs index 46cc6e1fa76..55bfd3e9dd5 100644 --- a/lib/src/annotate.rs +++ b/lib/src/annotate.rs @@ -441,7 +441,8 @@ async fn get_file_contents( tree: &MergedTree, ) -> Result { let file_value = tree.path_value_async(path).await?; - let effective_file_value = materialize_tree_value(store, path, file_value).await?; + let effective_file_value = + materialize_tree_value(store, path, file_value, tree.labels()).await?; match effective_file_value { MaterializedTreeValue::File(mut file) => Ok(file.read_all(path).await?.into()), MaterializedTreeValue::FileConflict(file) => { @@ -454,7 +455,11 @@ async fn get_file_contents( same_change: SameChange::Accept, }, }; - Ok(materialize_merge_result_to_bytes(&file.contents, &options)) + Ok(materialize_merge_result_to_bytes( + &file.contents, + &file.labels, + &options, + )) } _ => Ok(BString::default()), } diff --git a/lib/src/backend.rs b/lib/src/backend.rs index 8287dcdcebc..48740063fb0 100644 --- a/lib/src/backend.rs +++ b/lib/src/backend.rs @@ -26,6 +26,7 @@ use futures::stream::BoxStream; use thiserror::Error; use tokio::io::AsyncRead; +use crate::conflict_labels::ConflictLabels; use crate::content_hash::ContentHash; use crate::hex_util; use crate::index::Index; @@ -152,24 +153,34 @@ pub struct SecureSig { pub type SigningFn<'a> = dyn FnMut(&[u8]) -> SignResult> + Send + 'a; -/// Identifies a merge of multiple trees. Can be read as a `MergedTree`. -// TODO: this type doesn't add anything over `Merge` currently, but conflict labels could be -// added here in the future if we also add them to `MergedTree`. +/// Identifies a merge of multiple trees, including conflict labels. Can be read +/// as a `MergedTree`. #[derive(ContentHash, Debug, PartialEq, Eq, Clone)] pub struct MergedTreeId { /// The tree id(s) of a merge tree tree_ids: Merge, + /// The labels for the sides of the merged tree + labels: ConflictLabels, } impl MergedTreeId { /// Create a resolved `MergedTreeId` from a single regular tree. pub fn resolved(tree_id: TreeId) -> Self { - Self::new(Merge::resolved(tree_id)) + Self::unlabeled(Merge::resolved(tree_id)) } - /// Create a `MergedTreeId` from a `Merge`. - pub fn new(tree_ids: Merge) -> Self { - Self { tree_ids } + /// Create a `MergedTreeId` from a `Merge` without conflict labels. + pub fn unlabeled(tree_ids: Merge) -> Self { + Self::new(tree_ids, ConflictLabels::unlabeled()) + } + + /// Create a `MergedTreeId` from a `Merge` with conflict labels. + pub fn new(tree_ids: Merge, labels: ConflictLabels) -> Self { + if let Some(num_sides) = labels.num_sides() { + assert!(!tree_ids.is_resolved()); + assert_eq!(tree_ids.num_sides(), num_sides); + } + Self { tree_ids, labels } } /// Returns the underlying `Merge`. @@ -177,10 +188,23 @@ impl MergedTreeId { &self.tree_ids } - /// Extracts the underlying `Merge`. + /// Extracts the underlying `Merge`, discarding any conflict labels. pub fn into_merge(self) -> Merge { self.tree_ids } + + /// Check whether a `MergedTreeId` has changes compared to another + /// `MergedTreeId`, ignoring any conflict labels. This is the comparison + /// that should be used to check if a commit is empty, since conflict labels + /// are just metadata which doesn't count as a file change. + pub fn has_changes(&self, other: &Self) -> bool { + self.tree_ids != other.tree_ids + } + + /// Returns this merge's conflict labels, if any. + pub fn labels(&self) -> &ConflictLabels { + &self.labels + } } #[derive(ContentHash, Debug, PartialEq, Eq, Clone, serde::Serialize)] diff --git a/lib/src/commit.rs b/lib/src/commit.rs index 2338106ed76..f145edcf845 100644 --- a/lib/src/commit.rs +++ b/lib/src/commit.rs @@ -206,6 +206,12 @@ impl Commit { .map(|sig| self.store.signer().verify(&self.id, &sig.data, &sig.sig)) .transpose() } + + /// A short string describing the commit to be used in conflict markers. + pub fn conflict_label(&self) -> String { + // Example: "nlqwxzwn 7dd24e73" + format!("{:.8} {:.8}", self.change_id(), self.id) + } } pub(crate) fn is_backend_commit_empty( @@ -214,7 +220,9 @@ pub(crate) fn is_backend_commit_empty( commit: &backend::Commit, ) -> BackendResult { if let [parent_id] = &*commit.parents { - return Ok(commit.root_tree == *store.get_commit(parent_id)?.tree_id()); + return Ok(!commit + .root_tree + .has_changes(store.get_commit(parent_id)?.tree_id())); } let parents: Vec<_> = commit .parents @@ -222,7 +230,7 @@ pub(crate) fn is_backend_commit_empty( .map(|id| store.get_commit(id)) .try_collect()?; let parent_tree = merge_commit_trees(repo, &parents).block_on()?; - Ok(commit.root_tree == parent_tree.id()) + Ok(!commit.root_tree.has_changes(&parent_tree.id())) } fn is_commit_empty_by_index(repo: &dyn Repo, id: &CommitId) -> BackendResult> { diff --git a/lib/src/conflict_labels.rs b/lib/src/conflict_labels.rs new file mode 100644 index 00000000000..6529e6dc6cc --- /dev/null +++ b/lib/src/conflict_labels.rs @@ -0,0 +1,168 @@ +// Copyright 2025 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. + +//! Labels for conflicted trees. + +use std::fmt; +use std::sync::Arc; + +use crate::content_hash::ContentHash; +use crate::merge::Merge; + +/// Optionally contains a set of labels for the terms of a conflict. Resolved +/// merges cannot be labeled. The conflict labels are reference-counted to make +/// them more efficient to clone. +#[derive(ContentHash, PartialEq, Eq, Clone)] +pub struct ConflictLabels { + labels: Option>>, +} + +impl ConflictLabels { + /// Create a `ConflictLabels` with no labels. + pub const fn unlabeled() -> Self { + Self { labels: None } + } + + /// Create a `ConflictLabels` from an optional `Merge`. If the merge + /// is resolved, the label will be discarded, since resolved merges cannot + /// have labels. + pub fn new(labels: Option>) -> Self { + Self { + labels: labels.filter(|merge| !merge.is_resolved()).map(Arc::new), + } + } + + /// Create a `ConflictLabels` from a `Vec`, with an empty vec + /// representing no labels. + pub fn from_vec(labels: Vec) -> Self { + let merge = (!labels.is_empty()).then(|| Merge::from_vec(labels)); + Self::new(merge) + } + + /// Returns true if there are labels present. + pub fn is_present(&self) -> bool { + self.labels.is_some() + } + + /// Returns the number of labeled sides, or `None` if unlabeled. + pub fn num_sides(&self) -> Option { + self.labels.as_ref().map(|labels| labels.num_sides()) + } + + /// Returns the underlying labels as an `Option<&Merge>`. + pub fn as_merge(&self) -> Option<&Merge> { + self.labels.as_ref().map(Arc::as_ref) + } + + /// Returns the underlying labels as an `Option>`, cloning if + /// necessary. + pub fn into_merge(self) -> Option> { + self.labels.map(Arc::unwrap_or_clone) + } + + /// Returns the conflict labels as a slice. If there are no labels, returns + /// an empty slice. + pub fn as_slice(&self) -> &[String] { + self.as_merge().map_or(&[], |labels| labels.as_slice()) + } + + /// Get the label for a side at an index. + pub fn get_add(&self, add_index: usize) -> Option<&str> { + self.as_merge() + .and_then(|merge| merge.get_add(add_index).map(String::as_str)) + } + + /// Get the label for a base at an index. + pub fn get_remove(&self, remove_index: usize) -> Option<&str> { + self.as_merge() + .and_then(|merge| merge.get_remove(remove_index).map(String::as_str)) + } + + /// Simplify a merge with the same number of sides while preserving the + /// conflict labels corresponding to each side of the merge. + pub fn simplify_with(&self, merge: &Merge) -> (Self, Merge) { + if let Some(labels) = self.as_merge() { + let (labels, simplified) = labels + .as_ref() + .zip(merge.as_ref()) + .simplify_by(|&(_label, item)| item) + .unzip(); + (Self::new(Some(labels.cloned())), simplified.cloned()) + } else { + let simplified = merge.simplify(); + (Self::unlabeled(), simplified) + } + } +} + +impl From>> for ConflictLabels { + fn from(value: Option>) -> Self { + Self::new(value) + } +} + +impl From>> for ConflictLabels { + fn from(value: Option>) -> Self { + Self::new(value.map(|labels| labels.map(|&label| label.to_owned()))) + } +} + +impl fmt::Debug for ConflictLabels { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(labels) = self.as_merge() { + f.debug_tuple("Labeled").field(&labels.as_slice()).finish() + } else { + write!(f, "Unlabeled") + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_conflict_labels_from_vec() { + // From empty vec for unlabeled + assert_eq!( + ConflictLabels::from_vec(vec![]), + ConflictLabels::unlabeled() + ); + // From non-empty vec of terms + assert_eq!( + ConflictLabels::from_vec(vec![ + String::from("left"), + String::from("base"), + String::from("right") + ]), + ConflictLabels::from(Some(Merge::from_vec(vec!["left", "base", "right"]))) + ); + } + + #[test] + fn test_conflict_labels_as_slice() { + // Empty slice for unlabeled + let empty: &[String] = &[]; + assert_eq!(ConflictLabels::unlabeled().as_slice(), empty); + // Slice of terms for labeled + assert_eq!( + ConflictLabels::from(Some(Merge::from_vec(vec!["left", "base", "right"]))).as_slice(), + &[ + String::from("left"), + String::from("base"), + String::from("right") + ] + ); + } +} diff --git a/lib/src/conflicts.rs b/lib/src/conflicts.rs index 6b3ef88b218..8e80674b4d6 100644 --- a/lib/src/conflicts.rs +++ b/lib/src/conflicts.rs @@ -38,6 +38,7 @@ use crate::backend::FileId; use crate::backend::SymlinkId; use crate::backend::TreeId; use crate::backend::TreeValue; +use crate::conflict_labels::ConflictLabels; use crate::copies::CopiesTreeDiffEntry; use crate::copies::CopiesTreeDiffEntryPath; use crate::diff::ContentDiff; @@ -45,6 +46,7 @@ use crate::diff::DiffHunk; use crate::diff::DiffHunkKind; use crate::files; use crate::files::MergeResult; +use crate::merge::Diff; use crate::merge::Merge; use crate::merge::MergedTreeValue; use crate::merge::SameChange; @@ -185,6 +187,8 @@ pub struct MaterializedFileConflictValue { pub unsimplified_ids: Merge>, /// Simplified file ids, in which redundant id pairs are dropped. pub ids: Merge>, + /// Simplified conflict labels, matching `ids`. + pub labels: ConflictLabels, /// File contents corresponding to the simplified `ids`. // TODO: or Vec<(FileId, Box)> so that caller can stop reading // when null bytes found? @@ -202,8 +206,9 @@ pub async fn materialize_tree_value( store: &Store, path: &RepoPath, value: MergedTreeValue, + conflict_labels: &ConflictLabels, ) -> BackendResult { - match materialize_tree_value_no_access_denied(store, path, value).await { + match materialize_tree_value_no_access_denied(store, path, value, conflict_labels).await { Err(BackendError::ReadAccessDenied { source, .. }) => { Ok(MaterializedTreeValue::AccessDenied(source)) } @@ -215,6 +220,7 @@ async fn materialize_tree_value_no_access_denied( store: &Store, path: &RepoPath, value: MergedTreeValue, + conflict_labels: &ConflictLabels, ) -> BackendResult { match value.into_resolved() { Ok(None) => Ok(MaterializedTreeValue::Absent), @@ -237,10 +243,14 @@ async fn materialize_tree_value_no_access_denied( } Ok(Some(TreeValue::GitSubmodule(id))) => Ok(MaterializedTreeValue::GitSubmodule(id)), Ok(Some(TreeValue::Tree(id))) => Ok(MaterializedTreeValue::Tree(id)), - Err(conflict) => match try_materialize_file_conflict_value(store, path, &conflict).await? { - Some(file) => Ok(MaterializedTreeValue::FileConflict(file)), - None => Ok(MaterializedTreeValue::OtherConflict { id: conflict }), - }, + Err(conflict) => { + match try_materialize_file_conflict_value(store, path, &conflict, conflict_labels) + .await? + { + Some(file) => Ok(MaterializedTreeValue::FileConflict(file)), + None => Ok(MaterializedTreeValue::OtherConflict { id: conflict }), + } + } } } @@ -250,18 +260,20 @@ pub async fn try_materialize_file_conflict_value( store: &Store, path: &RepoPath, conflict: &MergedTreeValue, + conflict_labels: &ConflictLabels, ) -> BackendResult> { let (Some(unsimplified_ids), Some(executable_bits)) = (conflict.to_file_merge(), conflict.to_executable_merge()) else { return Ok(None); }; - let ids = unsimplified_ids.simplify(); + let (labels, ids) = conflict_labels.simplify_with(&unsimplified_ids); let contents = extract_as_single_hunk(&ids, store, path).await?; let executable = resolve_file_executable(&executable_bits); Ok(Some(MaterializedFileConflictValue { unsimplified_ids, ids, + labels, contents, executable, copy_id: Some(CopyId::placeholder()), @@ -403,6 +415,7 @@ pub fn choose_materialized_conflict_marker_len>(single_hunk: &Mer pub fn materialize_merge_result>( single_hunk: &Merge, + labels: &ConflictLabels, output: &mut dyn Write, options: &ConflictMaterializeOptions, ) -> io::Result<()> { @@ -413,13 +426,14 @@ pub fn materialize_merge_result>( let marker_len = options .marker_len .unwrap_or_else(|| choose_materialized_conflict_marker_len(single_hunk)); - materialize_conflict_hunks(hunks, options.marker_style, marker_len, output) + materialize_conflict_hunks(hunks, options.marker_style, marker_len, labels, output) } } } pub fn materialize_merge_result_to_bytes>( single_hunk: &Merge, + labels: &ConflictLabels, options: &ConflictMaterializeOptions, ) -> BString { let merge_result = files::merge_hunks(single_hunk, &options.merge); @@ -430,8 +444,14 @@ pub fn materialize_merge_result_to_bytes>( .marker_len .unwrap_or_else(|| choose_materialized_conflict_marker_len(single_hunk)); let mut output = Vec::new(); - materialize_conflict_hunks(&hunks, options.marker_style, marker_len, &mut output) - .expect("writing to an in-memory buffer should never fail"); + materialize_conflict_hunks( + &hunks, + options.marker_style, + marker_len, + labels, + &mut output, + ) + .expect("writing to an in-memory buffer should never fail"); output.into() } } @@ -441,6 +461,7 @@ fn materialize_conflict_hunks( hunks: &[Merge], conflict_marker_style: ConflictMarkerStyle, conflict_marker_len: usize, + labels: &ConflictLabels, output: &mut dyn Write, ) -> io::Result<()> { let num_conflicts = hunks @@ -453,7 +474,7 @@ fn materialize_conflict_hunks( output.write_all(content)?; } else { conflict_index += 1; - let conflict_info = format!("Conflict {conflict_index} of {num_conflicts}"); + let conflict_info = format!("conflict {conflict_index} of {num_conflicts}"); match (conflict_marker_style, hunk.as_slice()) { // 2-sided conflicts can use Git-style conflict markers @@ -464,6 +485,7 @@ fn materialize_conflict_hunks( right, &conflict_info, conflict_marker_len, + labels, output, )?; } @@ -473,6 +495,7 @@ fn materialize_conflict_hunks( &conflict_info, conflict_marker_style, conflict_marker_len, + labels, output, )?; } @@ -488,13 +511,17 @@ fn materialize_git_style_conflict( right: &[u8], conflict_info: &str, conflict_marker_len: usize, + labels: &ConflictLabels, output: &mut dyn Write, ) -> io::Result<()> { write_conflict_marker( output, ConflictMarkerLineChar::ConflictStart, conflict_marker_len, - &format!("Side #1 ({conflict_info})"), + &format!( + "{} ({conflict_info})", + labels.get_add(0).unwrap_or("side #1") + ), )?; write_and_ensure_newline(output, left)?; @@ -502,7 +529,7 @@ fn materialize_git_style_conflict( output, ConflictMarkerLineChar::GitAncestor, conflict_marker_len, - "Base", + labels.get_remove(0).unwrap_or("base"), )?; write_and_ensure_newline(output, base)?; @@ -519,7 +546,10 @@ fn materialize_git_style_conflict( output, ConflictMarkerLineChar::ConflictEnd, conflict_marker_len, - &format!("Side #2 ({conflict_info} ends)"), + &format!( + "{} ({conflict_info} ends)", + labels.get_add(1).unwrap_or("side #2") + ), )?; Ok(()) @@ -530,37 +560,56 @@ fn materialize_jj_style_conflict( conflict_info: &str, conflict_marker_style: ConflictMarkerStyle, conflict_marker_len: usize, + labels: &ConflictLabels, output: &mut dyn Write, ) -> io::Result<()> { + let get_side_label = |add_index: usize| -> String { + labels.get_add(add_index).map_or_else( + || format!("side #{}", add_index + 1), + |label| label.to_owned(), + ) + }; + + let get_base_label = |base_index: usize| -> String { + labels + .get_remove(base_index) + .map(|label| label.to_owned()) + .unwrap_or_else(|| { + // The vast majority of conflicts one actually tries to resolve manually have 1 + // base. + if hunk.removes().len() == 1 { + "base".to_string() + } else { + format!("base #{}", base_index + 1) + } + }) + }; + // Write a positive snapshot (side) of a conflict let write_side = |add_index: usize, data: &[u8], output: &mut dyn Write| { write_conflict_marker( output, ConflictMarkerLineChar::Add, conflict_marker_len, - &format!( - "Contents of side #{}{}", - add_index + 1, - maybe_no_eol_comment(data) - ), + &(get_side_label(add_index) + maybe_no_eol_comment(data)), )?; write_and_ensure_newline(output, data) }; // Write a negative snapshot (base) of a conflict - let write_base = |base_str: &str, data: &[u8], output: &mut dyn Write| { + let write_base = |base_index: usize, data: &[u8], output: &mut dyn Write| { write_conflict_marker( output, ConflictMarkerLineChar::Remove, conflict_marker_len, - &format!("Contents of {base_str}{}", maybe_no_eol_comment(data)), + &(get_base_label(base_index) + maybe_no_eol_comment(data)), )?; write_and_ensure_newline(output, data) }; // Write a diff from a negative term to a positive term let write_diff = - |base_str: &str, add_index: usize, diff: &[DiffHunk], output: &mut dyn Write| { + |base_index: usize, add_index: usize, diff: &[DiffHunk], output: &mut dyn Write| { let no_eol_remove = diff .last() .is_some_and(|diff_hunk| has_no_eol(diff_hunk.contents[0])); @@ -578,8 +627,9 @@ fn materialize_jj_style_conflict( ConflictMarkerLineChar::Diff, conflict_marker_len, &format!( - "Changes from {base_str} to side #{}{no_eol_comment}", - add_index + 1 + "{} compared with {}{no_eol_comment}", + get_side_label(add_index), + get_base_label(base_index) ), )?; write_diff_hunks(diff, output) @@ -599,20 +649,12 @@ fn materialize_jj_style_conflict( base_index }; - // The vast majority of conflicts one actually tries to resolve manually have 1 - // base. - let base_str = if hunk.removes().len() == 1 { - "base".to_string() - } else { - format!("base #{}", base_index + 1) - }; - let right1 = hunk.get_add(add_index).unwrap(); // For any style other than "diff", always emit sides and bases separately if conflict_marker_style != ConflictMarkerStyle::Diff { write_side(add_index, right1, output)?; - write_base(&base_str, left, output)?; + write_base(base_index, left, output)?; continue; } @@ -627,13 +669,13 @@ fn materialize_jj_style_conflict( // If the next positive term is a better match, emit the current positive term // as a snapshot and the next positive term as a diff. write_side(add_index, right1, output)?; - write_diff(&base_str, add_index + 1, &diff2, output)?; + write_diff(base_index, add_index + 1, &diff2, output)?; snapshot_written = true; continue; } } - write_diff(&base_str, add_index, &diff1, output)?; + write_diff(base_index, add_index, &diff1, output)?; } // If we still didn't emit a snapshot, the last side is the snapshot. @@ -691,6 +733,7 @@ pub struct MaterializedTreeDiffEntry { pub fn materialized_diff_stream( store: &Store, tree_diff: BoxStream<'_, CopiesTreeDiffEntry>, + conflict_labels: Diff<&ConflictLabels>, ) -> impl Stream { tree_diff .map(async |CopiesTreeDiffEntry { path, values }| match values { @@ -699,8 +742,18 @@ pub fn materialized_diff_stream( values: Err(err), }, Ok(values) => { - let before_future = materialize_tree_value(store, path.source(), values.before); - let after_future = materialize_tree_value(store, path.target(), values.after); + let before_future = materialize_tree_value( + store, + path.source(), + values.before, + conflict_labels.before, + ); + let after_future = materialize_tree_value( + store, + path.target(), + values.after, + conflict_labels.after, + ); let values = try_join!(before_future, after_future); MaterializedTreeDiffEntry { path, values } } diff --git a/lib/src/content_hash.rs b/lib/src/content_hash.rs index 0137ac009c9..0d8536e8fba 100644 --- a/lib/src/content_hash.rs +++ b/lib/src/content_hash.rs @@ -1,5 +1,7 @@ //! Portable, stable hashing suitable for identifying values +use std::sync::Arc; + use blake2::Blake2b512; // Re-export DigestUpdate so that the ContentHash proc macro can be used in // external crates without directly depending on the digest crate. @@ -128,6 +130,12 @@ impl ContentHash for Option { } } +impl ContentHash for Arc { + fn hash(&self, state: &mut impl DigestUpdate) { + ::hash(self, state); + } +} + impl ContentHash for std::collections::HashMap where K: ContentHash + Ord, diff --git a/lib/src/default_index/changed_path.rs b/lib/src/default_index/changed_path.rs index 517b72e121f..a64d1733bf9 100644 --- a/lib/src/default_index/changed_path.rs +++ b/lib/src/default_index/changed_path.rs @@ -569,7 +569,7 @@ pub(super) async fn collect_changed_paths( ) -> BackendResult> { let parents: Vec<_> = commit.parents_async().await?; if let [p] = parents.as_slice() - && commit.tree_id() == p.tree_id() + && !commit.tree_id().has_changes(p.tree_id()) { return Ok(vec![]); } diff --git a/lib/src/default_index/revset_engine.rs b/lib/src/default_index/revset_engine.rs index 6334f385f1d..2533074d633 100644 --- a/lib/src/default_index/revset_engine.rs +++ b/lib/src/default_index/revset_engine.rs @@ -42,6 +42,7 @@ use crate::backend::ChangeId; use crate::backend::CommitId; use crate::backend::MillisSinceEpoch; use crate::commit::Commit; +use crate::conflict_labels::ConflictLabels; use crate::conflicts::MaterializedTreeValue; use crate::conflicts::materialize_tree_value; use crate::diff::ContentDiff; @@ -1335,10 +1336,10 @@ async fn has_diff_from_parent( let parents: Vec<_> = commit.parents_async().await?; if let [parent] = parents.as_slice() { // Fast path: no need to load the root tree - let unchanged = commit.tree_id() == parent.tree_id(); + let changed = commit.tree_id().has_changes(parent.tree_id()); if matcher.visit(RepoPath::root()) == Visit::AllRecursively { - return Ok(!unchanged); - } else if unchanged { + return Ok(changed); + } else if !changed { return Ok(false); } } @@ -1382,8 +1383,11 @@ async fn matches_diff_from_parent( if !values.is_changed() { continue; } - let left_future = materialize_tree_value(store, &entry.path, values.before); - let right_future = materialize_tree_value(store, &entry.path, values.after); + let conflict_labels = ConflictLabels::unlabeled(); + let left_future = + materialize_tree_value(store, &entry.path, values.before, &conflict_labels); + let right_future = + materialize_tree_value(store, &entry.path, values.after, &conflict_labels); let (left_value, right_value) = futures::try_join!(left_future, right_future)?; let left_contents = to_file_content(&entry.path, left_value).await?; let right_contents = to_file_content(&entry.path, right_value).await?; diff --git a/lib/src/diff_presentation/mod.rs b/lib/src/diff_presentation/mod.rs index 738380909ff..c16a21a939a 100644 --- a/lib/src/diff_presentation/mod.rs +++ b/lib/src/diff_presentation/mod.rs @@ -24,6 +24,7 @@ use itertools::Itertools as _; use pollster::FutureExt as _; use crate::backend::BackendResult; +use crate::conflict_labels::ConflictLabels; use crate::conflicts::MaterializedFileValue; use crate::diff::CompareBytesExactly; use crate::diff::CompareBytesIgnoreAllWhitespace; @@ -54,9 +55,9 @@ pub struct FileContent { pub contents: T, } -impl FileContent> { +impl FileContent<(Merge, ConflictLabels)> { pub fn is_empty(&self) -> bool { - self.contents.as_resolved().is_some_and(|c| c.is_empty()) + self.contents.0.as_resolved().is_some_and(|c| c.is_empty()) } } diff --git a/lib/src/diff_presentation/unified.rs b/lib/src/diff_presentation/unified.rs index 324fd3ff9d7..5239f7f29dc 100644 --- a/lib/src/diff_presentation/unified.rs +++ b/lib/src/diff_presentation/unified.rs @@ -112,7 +112,11 @@ pub fn git_diff_part( hash = DUMMY_HASH.to_owned(); content = FileContent { is_binary: false, // TODO: are we sure this is never binary? - contents: materialize_merge_result_to_bytes(&file.contents, materialize_options), + contents: materialize_merge_result_to_bytes( + &file.contents, + &file.labels, + materialize_options, + ), }; } MaterializedTreeValue::OtherConflict { id } => { diff --git a/lib/src/git_backend.rs b/lib/src/git_backend.rs index 12a52511a49..905d5a5d87d 100644 --- a/lib/src/git_backend.rs +++ b/lib/src/git_backend.rs @@ -72,6 +72,7 @@ use crate::backend::TreeId; use crate::backend::TreeValue; use crate::backend::make_root_commit; use crate::config::ConfigGetError; +use crate::conflict_labels::ConflictLabels; use crate::file_util; use crate::file_util::BadPathEncoding; use crate::file_util::IoResultExt as _; @@ -98,6 +99,7 @@ const CHANGE_ID_LENGTH: usize = 16; const NO_GC_REF_NAMESPACE: &str = "refs/jj/keep/"; pub const JJ_TREES_COMMIT_HEADER: &str = "jj:trees"; +pub const JJ_CONFLICT_LABELS_COMMIT_HEADER: &str = "jj:conflict-labels"; pub const CHANGE_ID_COMMIT_HEADER: &str = "change-id"; #[derive(Debug, Error)] @@ -539,7 +541,10 @@ fn gix_open_opts_from_settings(settings: &UserSettings) -> gix::open::Options { } /// Parses the `jj:trees` header value. -fn root_tree_from_git_extra_header(value: &BStr) -> Result { +fn root_tree_from_git_extra_header( + value: &BStr, + conflict_labels: ConflictLabels, +) -> Result { let mut tree_ids = SmallVec::new(); for hex in value.split(|b| *b == b' ') { let tree_id = TreeId::try_from_hex(hex).ok_or(())?; @@ -554,7 +559,10 @@ fn root_tree_from_git_extra_header(value: &BStr) -> Result { if tree_ids.len() == 1 || tree_ids.len() % 2 == 0 { return Err(()); } - Ok(MergedTreeId::new(Merge::from_vec(tree_ids))) + Ok(MergedTreeId::new( + Merge::from_vec(tree_ids), + conflict_labels, + )) } fn commit_from_git_without_root_parent( @@ -582,6 +590,19 @@ fn commit_from_git_without_root_parent( .map(|oid| CommitId::from_bytes(oid.as_bytes())) .collect_vec() }; + // If the commit is a conflict, the conflict labels are stored in a commit + // header separately from the trees. + let conflict_labels: ConflictLabels = commit + .extra_headers() + .find(JJ_CONFLICT_LABELS_COMMIT_HEADER) + .map(|header| { + str::from_utf8(header) + .expect("labels should be valid utf8") + .split_terminator('\n') + .collect::>() + .build() + }) + .into(); // Conflicted commits written before we started using the `jj:trees` header // (~March 2024) may have the root trees stored in the extra metadata table // instead. For such commits, we'll update the root tree later when we read the @@ -589,7 +610,7 @@ fn commit_from_git_without_root_parent( let root_tree = commit .extra_headers() .find(JJ_TREES_COMMIT_HEADER) - .map(root_tree_from_git_extra_header) + .map(|header| root_tree_from_git_extra_header(header, conflict_labels)) .transpose() .map_err(|()| to_read_object_err("Invalid jj:trees header", id))? .unwrap_or_else(|| { @@ -748,7 +769,7 @@ fn deserialize_extras(commit: &mut Commit, bytes: &[u8]) { .iter() .map(|id_bytes| TreeId::from_bytes(id_bytes)) .collect(); - commit.root_tree = MergedTreeId::new(merge_builder.build()); + commit.root_tree = MergedTreeId::unlabeled(merge_builder.build()); } for predecessor in &proto.predecessors { commit.predecessors.push(CommitId::from_bytes(predecessor)); @@ -1256,6 +1277,19 @@ impl Backend for GitBackend { } let mut extra_headers: Vec<(BString, BString)> = vec![]; if !tree_ids.is_resolved() { + if let Some(conflict_labels) = contents.root_tree.labels().as_merge() { + // We use '\n' to separate the labels in the header, so they cannot contain '\n' + // and they should not be empty (since trailing newline might be stripped). + assert!( + conflict_labels + .iter() + .all(|label| !label.is_empty() && !label.contains('\n')) + ); + extra_headers.push(( + JJ_CONFLICT_LABELS_COMMIT_HEADER.into(), + conflict_labels.iter().join("\n").into(), + )); + } let value = tree_ids.iter().map(|id| id.hex()).join(" "); extra_headers.push((JJ_TREES_COMMIT_HEADER.into(), value.into())); } @@ -2080,7 +2114,7 @@ mod tests { let mut commit = Commit { parents: vec![backend.root_commit_id().clone()], predecessors: vec![], - root_tree: MergedTreeId::new(root_tree.clone()), + root_tree: MergedTreeId::unlabeled(root_tree.clone()), change_id: ChangeId::from_hex("abc123"), description: "".to_string(), author: create_signature(), diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 3fcc088a6e6..aed05c51c77 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -36,6 +36,7 @@ pub mod commit; pub mod commit_builder; pub mod config; mod config_resolver; +pub mod conflict_labels; pub mod conflicts; pub mod copies; pub mod dag_walk; diff --git a/lib/src/local_working_copy.rs b/lib/src/local_working_copy.rs index 4429faea8f8..9317f631766 100644 --- a/lib/src/local_working_copy.rs +++ b/lib/src/local_working_copy.rs @@ -68,6 +68,7 @@ use crate::backend::TreeId; use crate::backend::TreeValue; use crate::commit::Commit; use crate::config::ConfigGetError; +use crate::conflict_labels::ConflictLabels; use crate::conflicts; use crate::conflicts::ConflictMarkerStyle; use crate::conflicts::ConflictMaterializeOptions; @@ -977,7 +978,10 @@ impl TreeState { .iter() .map(|id| TreeId::new(id.clone())) .collect(); - self.tree_id = MergedTreeId::new(tree_ids_builder.build()); + self.tree_id = MergedTreeId::new( + tree_ids_builder.build(), + ConflictLabels::from_vec(proto.conflict_labels), + ); } self.file_states = FileStatesMap::from_proto(proto.file_states, proto.is_file_states_sorted); @@ -995,6 +999,7 @@ impl TreeState { .iter() .map(|id| id.to_bytes()) .collect(); + proto.conflict_labels = self.tree_id.labels().as_slice().to_owned(); proto.file_states = self.file_states.data.clone(); // `FileStatesMap` is guaranteed to be sorted. proto.is_file_states_sorted = true; @@ -1938,11 +1943,16 @@ impl TreeState { }; let mut changed_file_states = Vec::new(); let mut deleted_files = HashSet::new(); + // If a conflicted file didn't change between the two trees, but the conflict + // labels did, we still need to re-materialize it in the working copy. + let include_unchanged_conflicts = old_tree.labels() != new_tree.labels(); let mut diff_stream = old_tree - .diff_stream_for_file_system(new_tree, matcher) + .diff_stream_for_file_system(new_tree, matcher, include_unchanged_conflicts) .map(async |TreeDiffEntry { path, values }| match values { Ok(diff) => { - let result = materialize_tree_value(&self.store, &path, diff.after).await; + let result = + materialize_tree_value(&self.store, &path, diff.after, new_tree.labels()) + .await; (path, result.map(|value| (diff.before, value))) } Err(err) => (path, Err(err)), @@ -2030,7 +2040,8 @@ impl TreeState { marker_len: Some(conflict_marker_len), merge: self.store.merge_options().clone(), }; - let contents = materialize_merge_result_to_bytes(&file.contents, &options); + let contents = + materialize_merge_result_to_bytes(&file.contents, &file.labels, &options); let mut file_state = self .write_conflict(&disk_path, &contents, file.executable.unwrap_or(false)) .await?; @@ -2066,7 +2077,8 @@ impl TreeState { let matcher = self.sparse_matcher(); let mut changed_file_states = Vec::new(); let mut deleted_files = HashSet::new(); - let mut diff_stream = old_tree.diff_stream_for_file_system(new_tree, matcher.as_ref()); + let mut diff_stream = + old_tree.diff_stream_for_file_system(new_tree, matcher.as_ref(), false); while let Some(TreeDiffEntry { path, values }) = diff_stream.next().await { let after = values?.after; if after.is_absent() { diff --git a/lib/src/merge.rs b/lib/src/merge.rs index 016e78ea114..f21f9fc3436 100644 --- a/lib/src/merge.rs +++ b/lib/src/merge.rs @@ -22,6 +22,7 @@ use std::fmt::Formatter; use std::fmt::Write as _; use std::future::Future; use std::hash::Hash; +use std::iter; use std::iter::zip; use std::slice; use std::sync::Arc; @@ -29,6 +30,7 @@ use std::sync::Arc; use futures::future::try_join_all; use itertools::Itertools as _; use smallvec::SmallVec; +use smallvec::smallvec; use smallvec::smallvec_inline; use crate::backend::BackendResult; @@ -45,7 +47,7 @@ use crate::tree::Tree; /// /// This is not a diff in the `patch(1)` sense. See `diff::ContentDiff` for /// that. -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Diff { /// The state before pub before: T, @@ -195,6 +197,15 @@ impl Merge { Self { values } } + /// Creates a `Merge` from a first side and a series of diffs to apply to + /// that side. + pub fn from_diffs(first_side: T, diffs: impl IntoIterator>) -> Self { + let values = iter::once(first_side) + .chain(diffs.into_iter().flat_map(|diff| [diff.before, diff.after])) + .collect(); + Self { values } + } + /// Creates a `Merge` with a single resolved value. pub const fn resolved(value: T) -> Self { Self { @@ -202,6 +213,16 @@ impl Merge { } } + /// Creates a `Merge` by repeating a single value. + pub fn repeated(value: T, num_sides: usize) -> Self + where + T: Clone, + { + Self { + values: smallvec![value; num_sides * 2 - 1], + } + } + /// 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( @@ -320,14 +341,12 @@ impl Merge { simplified_to_original_indices } - /// Simplify the merge by joining diffs like A->B and B->C into A->C. - /// Also drops trivial diffs like A->A. + /// Apply the mapping returned by [`Self::get_simplified_mapping`]. #[must_use] - pub fn simplify(&self) -> Self + fn apply_simplified_mapping(&self, mapping: &[usize]) -> Self where - T: PartialEq + Clone, + T: Clone, { - let mapping = self.get_simplified_mapping(); // Reorder values based on their new indices in the simplified merge. let values = mapping .iter() @@ -336,6 +355,28 @@ impl Merge { Self { values } } + /// Simplify the merge by joining diffs like A->B and B->C into A->C. + /// Also drops trivial diffs like A->A. + #[must_use] + pub fn simplify(&self) -> Self + where + T: PartialEq + Clone, + { + let mapping = self.get_simplified_mapping(); + self.apply_simplified_mapping(&mapping) + } + + /// Simplify the merge, using a function to choose which values to compare. + #[must_use] + pub fn simplify_by<'a, U>(&'a self, f: impl FnMut(&'a T) -> U) -> Self + where + T: Clone, + U: PartialEq, + { + let mapping = self.map(f).get_simplified_mapping(); + self.apply_simplified_mapping(&mapping) + } + /// Updates the merge based on the given simplified merge. pub fn update_from_simplified(mut self, simplified: Self) -> Self where @@ -420,6 +461,38 @@ impl Merge { values: values.into(), }) } + + /// Converts a `&Merge` into a `Merge<&T>`. + pub fn as_ref(&self) -> Merge<&T> { + let values = self.values.iter().collect(); + Merge { values } + } + + /// Zip two merges which have the same number of terms. Panics if the merges + /// don't have the same number of terms. + pub fn zip(self, other: Merge) -> Merge<(T, U)> { + assert_eq!(self.values.len(), other.values.len()); + let values = self.values.into_iter().zip(other.values).collect(); + Merge { values } + } +} + +impl Merge<(T, U)> { + /// Unzips a merge of pairs into a pair of merges. + pub fn unzip(self) -> (Merge, Merge) { + let (left, right) = self.values.into_iter().unzip(); + (Merge { values: left }, Merge { values: right }) + } +} + +impl Merge<&'_ T> { + /// Convert a `Merge<&T>` into a `Merge` by cloning each term. + pub fn cloned(&self) -> Merge + where + T: Clone, + { + self.map(|&term| term.clone()) + } } /// Helper for consuming items from an iterator and then creating a `Merge`. @@ -500,6 +573,14 @@ impl Merge> { self.as_resolved()?.as_ref() } + /// Convert a `Merge>` into an `Option>`. + pub fn transpose(self) -> Option> { + self.values + .into_iter() + .collect::>() + .map(|values| Merge { values }) + } + /// Creates lists of `removes` and `adds` from a `Merge` by dropping /// `None` values. Note that the conversion is lossy: the order of `None` /// values is not preserved when converting back to a `Merge`. @@ -1050,6 +1131,46 @@ mod tests { assert_eq!(c(&[0, 1, 2, 3, 4, 5, 1]).simplify(), c(&[0, 3, 4, 5, 2])); } + #[test] + fn test_simplify_by() { + fn enumerate_and_simplify_by(merge: Merge) -> Merge<(usize, i32)> { + let enumerated = Merge::from_vec(merge.iter().copied().enumerate().collect_vec()); + enumerated.simplify_by(|&(_index, value)| value) + } + + // 1-way merge + assert_eq!(enumerate_and_simplify_by(c(&[0])), c(&[(0, 0)])); + // 3-way merge + assert_eq!(enumerate_and_simplify_by(c(&[1, 0, 0])), c(&[(0, 1)])); + assert_eq!( + enumerate_and_simplify_by(c(&[1, 0, 2])), + c(&[(0, 1), (1, 0), (2, 2)]) + ); + // 5-way merge + assert_eq!(enumerate_and_simplify_by(c(&[0, 0, 0, 0, 0])), c(&[(4, 0)])); + assert_eq!(enumerate_and_simplify_by(c(&[0, 0, 0, 0, 1])), c(&[(4, 1)])); + assert_eq!( + enumerate_and_simplify_by(c(&[0, 0, 0, 1, 2])), + c(&[(2, 0), (3, 1), (4, 2)]) + ); + assert_eq!( + enumerate_and_simplify_by(c(&[0, 1, 2, 2, 0])), + c(&[(0, 0), (1, 1), (4, 0)]) + ); + assert_eq!( + enumerate_and_simplify_by(c(&[0, 1, 2, 2, 2])), + c(&[(0, 0), (1, 1), (4, 2)]) + ); + assert_eq!( + enumerate_and_simplify_by(c(&[0, 1, 2, 2, 3])), + c(&[(0, 0), (1, 1), (4, 3)]) + ); + assert_eq!( + enumerate_and_simplify_by(c(&[0, 1, 2, 3, 4])), + c(&[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]) + ); + } + #[test] fn test_update_from_simplified() { // 1-way merge @@ -1232,4 +1353,41 @@ mod tests { c(&[0, 1, 2, 5, 4, 3, 6, 7, 8]) ); } + + #[test] + fn test_zip() { + // Zip of 1-way merges + assert_eq!(c(&[1]).zip(c(&[2])), c(&[(1, 2)])); + // Zip of 3-way merges + assert_eq!( + c(&[1, 2, 3]).zip(c(&[4, 5, 6])), + c(&[(1, 4), (2, 5), (3, 6)]) + ); + } + + #[test] + fn test_unzip() { + // 1-way merge + assert_eq!(c(&[(1, 2)]).unzip(), (c(&[1]), c(&[2]))); + // 3-way merge + assert_eq!( + c(&[(1, 4), (2, 5), (3, 6)]).unzip(), + (c(&[1, 2, 3]), c(&[4, 5, 6])) + ); + } + + #[test] + fn test_transpose() { + // 1-way merge + assert_eq!(c::>(&[None]).transpose(), None); + assert_eq!(c(&[Some(1)]).transpose(), Some(c(&[1]))); + // 3-way merge + assert_eq!(c::>(&[None, None, None]).transpose(), None); + assert_eq!(c(&[Some(1), None, Some(3)]).transpose(), None); + assert_eq!(c(&[Some(1), Some(2), None]).transpose(), None); + assert_eq!( + c(&[Some(1), Some(2), Some(3)]).transpose(), + Some(c(&[1, 2, 3])) + ); + } } diff --git a/lib/src/merged_tree.rs b/lib/src/merged_tree.rs index 87a411e66dd..17dedc173fa 100644 --- a/lib/src/merged_tree.rs +++ b/lib/src/merged_tree.rs @@ -38,6 +38,7 @@ use crate::backend::BackendResult; use crate::backend::MergedTreeId; use crate::backend::TreeId; use crate::backend::TreeValue; +use crate::conflict_labels::ConflictLabels; use crate::copies::CopiesTreeDiffEntry; use crate::copies::CopiesTreeDiffStream; use crate::copies::CopyRecords; @@ -56,21 +57,30 @@ use crate::tree::Tree; use crate::tree_builder::TreeBuilder; use crate::tree_merge::merge_trees; -/// Presents a view of a merged set of trees. +/// Presents a view of a merged set of trees, including conflict labels. #[derive(PartialEq, Eq, Clone, Debug)] pub struct MergedTree { trees: Merge, + labels: ConflictLabels, } impl MergedTree { /// Creates a new `MergedTree` representing a single tree without conflicts. pub fn resolved(tree: Tree) -> Self { - Self::new(Merge::resolved(tree)) + Self::unlabeled(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(trees: Merge) -> Self { + /// Creates a new `MergedTree` representing a merge of a set of trees + /// without conflict labels. The individual trees must not have any + /// conflicts. + pub fn unlabeled(trees: Merge) -> Self { + Self::new(trees, ConflictLabels::unlabeled()) + } + + /// Creates a new `MergedTree` representing a merge of a set of trees + /// with conflict labels. The individual trees must not have any + /// conflicts. + pub fn new(trees: Merge, labels: ConflictLabels) -> Self { debug_assert!(trees.iter().map(|tree| tree.dir()).all_equal()); debug_assert!( trees @@ -78,7 +88,10 @@ impl MergedTree { .map(|tree| Arc::as_ptr(tree.store())) .all_equal() ); - Self { trees } + if let Some(num_sides) = labels.num_sides() { + assert_eq!(trees.num_sides(), num_sides); + } + Self { trees, labels } } /// Returns the underlying `Merge`. @@ -86,11 +99,33 @@ impl MergedTree { &self.trees } - /// Extracts the underlying `Merge`. + /// Extracts the underlying `Merge`, discarding any conflict labels. pub fn into_merge(self) -> Merge { self.trees } + /// Returns this merge's conflict labels, if any. + pub fn labels(&self) -> &ConflictLabels { + &self.labels + } + + /// Returns optional labels for each term in a merge. If the merge is + /// resolved, returns `resolved_label` instead. + pub fn labels_by_term<'a>(&'a self, resolved_label: Option<&'a str>) -> Merge> { + if self.trees.is_resolved() { + assert!(!self.labels.is_present()); + Merge::resolved(resolved_label) + } else { + self.labels.as_merge().map_or_else( + || Merge::repeated(None, self.trees.num_sides()), + |labels| { + assert_eq!(labels.num_sides(), self.trees.num_sides()); + labels.map(|label| Some(label.as_str())) + }, + ) + } + } + /// This tree's directory pub fn dir(&self) -> &RepoPath { self.trees.first().dir() @@ -122,7 +157,11 @@ impl MergedTree { // a resolved merge. However, that function will always preserve the arity of // conflicts it cannot resolve. So we simplify the conflict again // here to possibly reduce a complex conflict to a simpler one. - let simplified = merged.simplify(); + let (simplified_labels, simplified) = if merged.is_resolved() { + (ConflictLabels::unlabeled(), merged) + } else { + self.labels.simplify_with(&merged) + }; // If debug assertions are enabled, check that the merge was idempotent. In // particular, that this last simplification doesn't enable further automatic // resolutions @@ -130,7 +169,10 @@ impl MergedTree { let re_merged = merge_trees(simplified.clone()).await.unwrap(); debug_assert_eq!(re_merged, simplified); } - Ok(Self { trees: simplified }) + Ok(Self { + trees: simplified, + labels: simplified_labels, + }) } /// An iterator over the conflicts in this tree, including subtrees. @@ -178,7 +220,10 @@ impl MergedTree { } }) .await?; - Ok(Some(Self { trees })) + Ok(Some(Self { + trees, + labels: self.labels.clone(), + })) } } } @@ -206,7 +251,10 @@ impl MergedTree { /// The tree's id pub fn id(&self) -> MergedTreeId { - MergedTreeId::new(self.trees.map(|tree| tree.id().clone())) + MergedTreeId::new( + self.trees.map(|tree| tree.id().clone()), + self.labels.clone(), + ) } /// Look up the tree at the given path. @@ -254,6 +302,7 @@ impl MergedTree { &self, other: &Self, matcher: &'matcher dyn Matcher, + include_unchanged_conflicts: bool, ) -> TreeDiffStream<'matcher> { let concurrency = self.store().concurrency(); if concurrency <= 1 { @@ -261,6 +310,7 @@ impl MergedTree { &self.trees, &other.trees, matcher, + include_unchanged_conflicts, ))) } else { Box::pin(TreeDiffStreamImpl::new( @@ -268,6 +318,7 @@ impl MergedTree { &other.trees, matcher, concurrency, + include_unchanged_conflicts, )) } } @@ -278,19 +329,23 @@ impl MergedTree { other: &Self, matcher: &'matcher dyn Matcher, ) -> TreeDiffStream<'matcher> { - stream_without_trees(self.diff_stream_internal(other, matcher)) + stream_without_trees(self.diff_stream_internal(other, matcher, false)) } /// Like `diff_stream()` but files in a removed tree will be returned before - /// a file that replaces it. + /// a file that replaces it. If `include_unchanged_conflicts` is true, then + /// conflicted files will be returned even if they are unchanged. pub fn diff_stream_for_file_system<'matcher>( &self, other: &Self, matcher: &'matcher dyn Matcher, + include_unchanged_conflicts: bool, ) -> TreeDiffStream<'matcher> { - Box::pin(DiffStreamForFileSystem::new( - self.diff_stream_internal(other, matcher), - )) + Box::pin(DiffStreamForFileSystem::new(self.diff_stream_internal( + other, + matcher, + include_unchanged_conflicts, + ))) } /// Like `diff_stream()` but takes the given copy records into account. @@ -310,18 +365,44 @@ impl MergedTree { } /// Merges this tree with `other`, using `base` as base. Any conflicts will - /// be resolved recursively if possible. - pub async fn merge(self, base: Self, other: Self) -> BackendResult { - self.merge_no_resolve(base, other).resolve().await + /// be resolved recursively if possible. Does not add conflict labels. + pub async fn merge_unlabeled(self, base: Self, other: Self) -> BackendResult { + Self::merge(Merge::from_vec(vec![ + (self, String::new()), + (base, String::new()), + (other, String::new()), + ])) + .await } - /// Merges this tree with `other`, using `base` as base, without attempting + /// Merges the provided trees into a single `MergedTree`. Any conflicts will + /// be resolved recursively if possible. The provided labels are used if a + /// conflict arises. However, if one of the input trees is already + /// conflicted, the corresponding label will be ignored, and its existing + /// labels will be used instead (or if any conflicted tree is unlabeled, + /// then the entire result will also be unlabeled). + pub async fn merge(merge: Merge<(Self, String)>) -> BackendResult { + Self::merge_no_resolve(merge).resolve().await + } + + /// Merges the provided trees into a single `MergedTree`, without attempting /// to resolve file conflicts. - pub fn merge_no_resolve(self, base: Self, other: Self) -> Self { - let nested = Merge::from_vec(vec![self.trees, base.trees, other.trees]); - Self { - trees: nested.flatten().simplify(), - } + pub fn merge_no_resolve(merge: Merge<(Self, String)>) -> Self { + let flattened_labels: ConflictLabels = merge + .map(|(tree, label)| tree.labels_by_term((!label.is_empty()).then_some(label.as_str()))) + .flatten() + .transpose() + .into(); + + let flattened_trees: Merge = merge + .into_iter() + .map(|(tree, _label)| tree.into_merge()) + .collect::>() + .build() + .flatten(); + + let (labels, trees) = flattened_labels.simplify_with(&flattened_trees); + Self { trees, labels } } } @@ -398,6 +479,7 @@ pub fn all_merged_tree_entries( fn merged_tree_entry_diff<'a>( trees1: &'a Merge, trees2: &'a Merge, + include_unchanged_conflicts: bool, ) -> impl Iterator>)> { itertools::merge_join_by( all_tree_entries(trees1), @@ -409,7 +491,9 @@ fn merged_tree_entry_diff<'a>( EitherOrBoth::Left((name, value1)) => (name, Diff::new(value1, Merge::absent())), EitherOrBoth::Right((name, value2)) => (name, Diff::new(Merge::absent(), value2)), }) - .filter(|(_, diff)| diff.is_changed()) + .filter(move |(_, diff)| { + diff.is_changed() || (include_unchanged_conflicts && !diff.after.is_resolved()) + }) } fn trees_value<'a>(trees: &'a Merge, basename: &RepoPathComponent) -> MergedTreeVal<'a> { @@ -565,6 +649,7 @@ pub struct TreeDiffIterator<'matcher> { store: Arc, stack: Vec, matcher: &'matcher dyn Matcher, + include_unchanged_conflicts: bool, } struct TreeDiffDir { @@ -573,17 +658,29 @@ struct TreeDiffDir { impl<'matcher> TreeDiffIterator<'matcher> { /// Creates a iterator over the differences between two trees. - pub fn new(trees1: &Merge, trees2: &Merge, matcher: &'matcher dyn Matcher) -> Self { + pub fn new( + trees1: &Merge, + trees2: &Merge, + matcher: &'matcher dyn Matcher, + include_unchanged_conflicts: bool, + ) -> Self { assert!(Arc::ptr_eq(trees1.first().store(), trees2.first().store())); let root_dir = RepoPath::root(); let mut stack = Vec::new(); if !matcher.visit(root_dir).is_nothing() { - stack.push(TreeDiffDir::from_trees(root_dir, trees1, trees2, matcher)); + stack.push(TreeDiffDir::from_trees( + root_dir, + trees1, + trees2, + matcher, + include_unchanged_conflicts, + )); }; Self { store: trees1.first().store().clone(), stack, matcher, + include_unchanged_conflicts, } } @@ -607,9 +704,10 @@ impl TreeDiffDir { trees1: &Merge, trees2: &Merge, matcher: &dyn Matcher, + include_unchanged_conflicts: bool, ) -> Self { let mut entries = vec![]; - for (name, diff) in merged_tree_entry_diff(trees1, trees2) { + for (name, diff) in merged_tree_entry_diff(trees1, trees2, include_unchanged_conflicts) { let path = dir.join(name); let tree_before = diff.before.is_tree(); let tree_after = diff.after.is_tree(); @@ -671,8 +769,13 @@ impl Iterator for TreeDiffIterator<'_> { }); } }; - let subdir = - TreeDiffDir::from_trees(&path, &before_tree, &after_tree, self.matcher); + let subdir = TreeDiffDir::from_trees( + &path, + &before_tree, + &after_tree, + self.matcher, + self.include_unchanged_conflicts, + ); self.stack.push(subdir); }; if diff.before.is_file_like() || diff.after.is_file_like() { @@ -713,6 +816,7 @@ pub struct TreeDiffStreamImpl<'matcher> { /// limit because we have a file item that's blocked by pending subdirectory /// items. max_queued_items: usize, + include_unchanged_conflicts: bool, } impl<'matcher> TreeDiffStreamImpl<'matcher> { @@ -723,6 +827,7 @@ impl<'matcher> TreeDiffStreamImpl<'matcher> { trees2: &Merge, matcher: &'matcher dyn Matcher, max_concurrent_reads: usize, + include_unchanged_conflicts: bool, ) -> Self { assert!(Arc::ptr_eq(trees1.first().store(), trees2.first().store())); let mut stream = Self { @@ -732,6 +837,7 @@ impl<'matcher> TreeDiffStreamImpl<'matcher> { pending_trees: BTreeMap::new(), max_concurrent_reads, max_queued_items: 10000, + include_unchanged_conflicts, }; stream.add_dir_diff_items(RepoPath::root(), trees1, trees2); stream @@ -764,7 +870,9 @@ impl<'matcher> TreeDiffStreamImpl<'matcher> { } fn add_dir_diff_items(&mut self, dir: &RepoPath, trees1: &Merge, trees2: &Merge) { - for (basename, diff) in merged_tree_entry_diff(trees1, trees2) { + for (basename, diff) in + merged_tree_entry_diff(trees1, trees2, self.include_unchanged_conflicts) + { let path = dir.join(basename); let tree_before = diff.before.is_tree(); let tree_after = diff.after.is_tree(); @@ -980,11 +1088,12 @@ impl MergedTreeBuilder { /// Create new tree(s) from the base tree(s) and overrides. pub fn write_tree(self, store: &Arc) -> BackendResult { let base_tree_ids = self.base_tree_id.as_merge().clone(); + let base_tree_labels = self.base_tree_id.labels().clone(); let new_tree_ids = self.write_merged_trees(base_tree_ids, store)?; match new_tree_ids.simplify().into_resolved() { Ok(single_tree_id) => Ok(MergedTreeId::resolved(single_tree_id)), Err(tree_id) => { - let tree = store.get_root_tree(&MergedTreeId::new(tree_id))?; + let tree = store.get_root_tree(&MergedTreeId::new(tree_id, base_tree_labels))?; let resolved = tree.resolve().block_on()?; Ok(resolved.id()) } diff --git a/lib/src/protos/local_working_copy.proto b/lib/src/protos/local_working_copy.proto index ce167613e91..713a6f63f5b 100644 --- a/lib/src/protos/local_working_copy.proto +++ b/lib/src/protos/local_working_copy.proto @@ -51,6 +51,8 @@ message TreeState { // Alternating positive and negative terms if there's a conflict, otherwise a // single (positive) value repeated bytes tree_ids = 5; + // Labels for the terms of a conflict. + repeated string conflict_labels = 7; repeated FileStateEntry file_states = 2; bool is_file_states_sorted = 6; SparsePatterns sparse_patterns = 3; diff --git a/lib/src/protos/local_working_copy.rs b/lib/src/protos/local_working_copy.rs index 4d552d2dfcc..a9dd9f4d9dc 100644 --- a/lib/src/protos/local_working_copy.rs +++ b/lib/src/protos/local_working_copy.rs @@ -37,6 +37,9 @@ pub struct TreeState { /// single (positive) value #[prost(bytes = "vec", repeated, tag = "5")] pub tree_ids: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, + /// Labels for the terms of a conflict. + #[prost(string, repeated, tag = "7")] + pub conflict_labels: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, #[prost(message, repeated, tag = "2")] pub file_states: ::prost::alloc::vec::Vec, #[prost(bool, tag = "6")] diff --git a/lib/src/protos/simple_store.proto b/lib/src/protos/simple_store.proto index 24b4fa63604..a7a6a4e5bff 100644 --- a/lib/src/protos/simple_store.proto +++ b/lib/src/protos/simple_store.proto @@ -44,6 +44,8 @@ message Commit { repeated bytes predecessors = 2; // Alternating positive and negative terms repeated bytes root_tree = 3; + // Labels for the terms of a conflict. + repeated string conflict_labels = 10; bytes change_id = 4; string description = 5; diff --git a/lib/src/protos/simple_store.rs b/lib/src/protos/simple_store.rs index fc6d1fb924d..8f1fdd5652b 100644 --- a/lib/src/protos/simple_store.rs +++ b/lib/src/protos/simple_store.rs @@ -49,6 +49,9 @@ pub struct Commit { /// Alternating positive and negative terms #[prost(bytes = "vec", repeated, tag = "3")] pub root_tree: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, + /// Labels for the terms of a conflict. + #[prost(string, repeated, tag = "10")] + pub conflict_labels: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, #[prost(bytes = "vec", tag = "4")] pub change_id: ::prost::alloc::vec::Vec, #[prost(string, tag = "5")] diff --git a/lib/src/rewrite.rs b/lib/src/rewrite.rs index b7dfb10b1ee..b5bd3a49068 100644 --- a/lib/src/rewrite.rs +++ b/lib/src/rewrite.rs @@ -40,6 +40,7 @@ use crate::index::IndexResult; use crate::iter_util::fallible_any; use crate::matchers::Matcher; use crate::matchers::Visit; +use crate::merge::Diff; use crate::merge::Merge; use crate::merged_tree::MergedTree; use crate::merged_tree::MergedTreeBuilder; @@ -76,14 +77,14 @@ pub async fn merge_commit_trees_no_resolve_without_repo( .map(|commit| commit.id().clone()) .collect_vec(); let commit_id_merge = find_recursive_merge_commits(store, index, commit_ids)?; - let tree_merge = commit_id_merge + let tree_merge: Merge<(MergedTree, String)> = commit_id_merge .try_map_async(async |commit_id| { let commit = store.get_commit_async(commit_id).await?; let tree = commit.tree_async().await?; - Ok::<_, BackendError>(tree.into_merge()) + Ok::<_, BackendError>((tree, commit.conflict_label())) }) .await?; - Ok(MergedTree::new(tree_merge.flatten().simplify())) + Ok(MergedTree::merge_no_resolve(tree_merge)) } /// Find the commits to use as input to the recursive merge algorithm. @@ -282,9 +283,22 @@ impl<'repo> CommitRewriter<'repo> { let old_tree_fut = self.old_commit.tree_async(); let (old_base_tree, new_base_tree, old_tree) = try_join!(old_base_tree_fut, new_base_tree_fut, old_tree_fut)?; + let old_commit_label = self.old_commit.conflict_label(); ( - old_base_tree.id() == *self.old_commit.tree_id(), - new_base_tree.merge(old_base_tree, old_tree).await?.id(), + !old_base_tree.id().has_changes(self.old_commit.tree_id()), + MergedTree::merge(Merge::from_vec(vec![ + ( + new_base_tree, + format!( + "rebase destination ({})", + new_parents.iter().map(Commit::conflict_label).join(", ") + ), + ), + (old_base_tree, format!("parents of {old_commit_label}")), + (old_tree, format!("rebased commit ({old_commit_label})")), + ])) + .await? + .id(), ) }; // Ensure we don't abandon commits with multiple parents (merge commits), even @@ -292,8 +306,10 @@ impl<'repo> CommitRewriter<'repo> { if let [parent] = &new_parents[..] { let should_abandon = match empty { EmptyBehavior::Keep => false, - EmptyBehavior::AbandonNewlyEmpty => *parent.tree_id() == new_tree_id && !was_empty, - EmptyBehavior::AbandonAllEmpty => *parent.tree_id() == new_tree_id, + EmptyBehavior::AbandonNewlyEmpty => { + !parent.tree_id().has_changes(&new_tree_id) && !was_empty + } + EmptyBehavior::AbandonAllEmpty => !parent.tree_id().has_changes(&new_tree_id), }; if should_abandon { self.abandon(); @@ -378,7 +394,7 @@ pub fn rebase_to_dest_parent( let source_parent_tree = source.parent_tree(repo)?; let source_tree = source.tree()?; destination_tree - .merge(source_parent_tree, source_tree) + .merge_unlabeled(source_parent_tree, source_tree) .block_on() }, ) @@ -1130,7 +1146,7 @@ pub struct CommitWithSelection { impl CommitWithSelection { /// Returns true if the selection contains all changes in the commit. pub fn is_full_selection(&self) -> bool { - &self.selected_tree.id() == self.commit.tree_id() + !self.selected_tree.id().has_changes(self.commit.tree_id()) } /// Returns true if the selection matches the parent tree (contains no @@ -1139,7 +1155,21 @@ impl CommitWithSelection { /// Both `is_full_selection()` and `is_empty_selection()` /// can be true if the commit is itself empty. pub fn is_empty_selection(&self) -> bool { - self.selected_tree.id() == self.parent_tree.id() + !self.selected_tree.id().has_changes(&self.parent_tree.id()) + } + + /// Returns conflict labels for the diff represented by this selection. + pub fn conflict_labels(&self) -> BackendResult> { + let commit_label = self.commit.conflict_label(); + let parent_label = format!("parents of {commit_label}"); + if self.is_full_selection() { + Ok(Diff::new(parent_label, commit_label)) + } else { + Ok(Diff::new( + parent_label, + format!("selected changes from {commit_label}"), + )) + } } } @@ -1163,6 +1193,7 @@ pub fn squash_commits<'repo>( ) -> BackendResult>> { struct SourceCommit<'a> { commit: &'a CommitWithSelection, + labels: Diff, abandon: bool, } let mut source_commits = vec![]; @@ -1179,6 +1210,7 @@ pub fn squash_commits<'repo>( // squash -r`)? The source tree will be unchanged in that case. source_commits.push(SourceCommit { commit: source, + labels: source.conflict_labels()?, abandon, }); } @@ -1195,12 +1227,18 @@ pub fn squash_commits<'repo>( } else { let source_tree = source.commit.commit.tree()?; // Apply the reverse of the selected changes onto the source - let new_source_tree = source_tree - .merge( + let new_source_tree = MergedTree::merge(Merge::from_vec(vec![ + (source_tree, source.commit.commit.conflict_label()), + ( source.commit.selected_tree.clone(), + source.labels.after.clone(), + ), + ( source.commit.parent_tree.clone(), - ) - .block_on()?; + source.labels.before.clone(), + ), + ])) + .block_on()?; repo.rewrite_commit(&source.commit.commit) .set_tree_id(new_source_tree.id().clone()) .write()?; @@ -1230,22 +1268,29 @@ pub fn squash_commits<'repo>( }; })?; } - // Apply the selected changes onto the destination - let mut destination_tree = rewritten_destination.tree()?; - for source in &source_commits { - destination_tree = destination_tree - .merge( - source.commit.parent_tree.clone(), - source.commit.selected_tree.clone(), - ) - .block_on()?; - } let mut predecessors = vec![destination.id().clone()]; predecessors.extend( source_commits .iter() .map(|source| source.commit.commit.id().clone()), ); + // Apply the selected changes onto the destination + let destination_tree = MergedTree::merge(Merge::from_diffs( + ( + rewritten_destination.tree()?, + format!("squash destination ({})", destination.conflict_label()), + ), + source_commits.into_iter().map(|source| { + Diff::new( + (source.commit.parent_tree.clone(), source.labels.before), + ( + source.commit.selected_tree.clone(), + format!("squashed commit ({})", source.labels.after), + ), + ) + }), + )) + .block_on()?; let commit_builder = repo .rewrite_commit(&rewritten_destination) @@ -1329,7 +1374,7 @@ pub fn find_duplicate_divergent_commits( rebase_to_dest_parent(repo, slice::from_ref(target_commit), &ancestor_candidate)?; // Check whether the rebased commit would have the same tree as the existing // commit if they had the same parents. If so, we can skip this rebased commit. - if new_tree.id() == *ancestor_candidate.tree_id() { + if !new_tree.id().has_changes(ancestor_candidate.tree_id()) { duplicate_divergent.push(target_commit.clone()); break; } diff --git a/lib/src/simple_backend.rs b/lib/src/simple_backend.rs index ca452eb39d1..9f4794a7f82 100644 --- a/lib/src/simple_backend.rs +++ b/lib/src/simple_backend.rs @@ -57,6 +57,7 @@ use crate::backend::Tree; use crate::backend::TreeId; use crate::backend::TreeValue; use crate::backend::make_root_commit; +use crate::conflict_labels::ConflictLabels; use crate::content_hash::blake2b_hash; use crate::file_util::persist_content_addressed_temp_file; use crate::index::Index; @@ -365,6 +366,7 @@ pub fn commit_to_proto(commit: &Commit) -> crate::protos::simple_store::Commit { .iter() .map(|id| id.to_bytes()) .collect(); + proto.conflict_labels = commit.root_tree.labels().as_slice().to_owned(); proto.change_id = commit.change_id.to_bytes(); proto.description = commit.description.clone(); proto.author = Some(signature_to_proto(&commit.author)); @@ -383,7 +385,10 @@ fn commit_from_proto(mut proto: crate::protos::simple_store::Commit) -> Commit { let parents = proto.parents.into_iter().map(CommitId::new).collect(); let predecessors = proto.predecessors.into_iter().map(CommitId::new).collect(); let merge_builder: MergeBuilder<_> = proto.root_tree.into_iter().map(TreeId::new).collect(); - let root_tree = MergedTreeId::new(merge_builder.build()); + let root_tree = MergedTreeId::new( + merge_builder.build(), + ConflictLabels::from_vec(proto.conflict_labels), + ); let change_id = ChangeId::new(proto.change_id); Commit { parents, diff --git a/lib/src/store.rs b/lib/src/store.rs index 8b75f5f8346..c2a62d79dca 100644 --- a/lib/src/store.rs +++ b/lib/src/store.rs @@ -227,7 +227,7 @@ impl Store { .as_merge() .try_map_async(|id| self.get_tree_async(RepoPathBuf::root(), id)) .await?; - Ok(MergedTree::new(trees)) + Ok(MergedTree::new(trees, id.labels().clone())) } pub async fn write_tree( diff --git a/lib/tests/test_conflicts.rs b/lib/tests/test_conflicts.rs index c70cf0d631e..1f687343ec6 100644 --- a/lib/tests/test_conflicts.rs +++ b/lib/tests/test_conflicts.rs @@ -15,6 +15,7 @@ use indoc::indoc; use itertools::Itertools as _; use jj_lib::backend::FileId; +use jj_lib::conflict_labels::ConflictLabels; use jj_lib::conflicts::ConflictMarkerStyle; use jj_lib::conflicts::ConflictMaterializeOptions; use jj_lib::conflicts::MIN_CONFLICT_MARKER_LEN; @@ -89,15 +90,15 @@ fn test_materialize_conflict_basic() { @r" line 1 line 2 - <<<<<<< Conflict 1 of 1 - +++++++ Contents of side #1 + <<<<<<< conflict 1 of 1 + +++++++ side #1 left 3.1 left 3.2 left 3.3 - %%%%%%% Changes from base to side #2 + %%%%%%% side #2 compared with base -line 3 +right 3.1 - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends line 4 line 5 " @@ -113,15 +114,15 @@ fn test_materialize_conflict_basic() { @r" line 1 line 2 - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% side #1 compared with base -line 3 +right 3.1 - +++++++ Contents of side #2 + +++++++ side #2 left 3.1 left 3.2 left 3.3 - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends line 4 line 5 " @@ -136,16 +137,16 @@ fn test_materialize_conflict_basic() { @r" line 1 line 2 - <<<<<<< Conflict 1 of 1 - +++++++ Contents of side #1 + <<<<<<< conflict 1 of 1 + +++++++ side #1 left 3.1 left 3.2 left 3.3 - ------- Contents of base + ------- base line 3 - +++++++ Contents of side #2 + +++++++ side #2 right 3.1 - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends line 4 line 5 " @@ -160,15 +161,15 @@ fn test_materialize_conflict_basic() { @r" line 1 line 2 - <<<<<<< Side #1 (Conflict 1 of 1) + <<<<<<< side #1 (conflict 1 of 1) left 3.1 left 3.2 left 3.3 - ||||||| Base + ||||||| base line 3 ======= right 3.1 - >>>>>>> Side #2 (Conflict 1 of 1 ends) + >>>>>>> side #2 (conflict 1 of 1 ends) line 4 line 5 " @@ -243,21 +244,21 @@ fn test_materialize_conflict_three_sides() { &materialize_conflict_string(store, path, &conflict, ConflictMarkerStyle::Diff), @r" line 1 - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base #1 to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% side #1 compared with base #1 -line 2 base -line 3 base +line 2 a.1 +line 3 a.2 line 4 base - +++++++ Contents of side #2 + +++++++ side #2 line 2 b.1 line 3 base line 4 b.2 - %%%%%%% Changes from base #2 to side #3 + %%%%%%% side #3 compared with base #2 line 2 base +line 3 c.2 - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends line 5 " ); @@ -266,25 +267,25 @@ fn test_materialize_conflict_three_sides() { &materialize_conflict_string(store, path, &conflict, ConflictMarkerStyle::Snapshot), @r" line 1 - <<<<<<< Conflict 1 of 1 - +++++++ Contents of side #1 + <<<<<<< conflict 1 of 1 + +++++++ side #1 line 2 a.1 line 3 a.2 line 4 base - ------- Contents of base #1 + ------- base #1 line 2 base line 3 base line 4 base - +++++++ Contents of side #2 + +++++++ side #2 line 2 b.1 line 3 base line 4 b.2 - ------- Contents of base #2 + ------- base #2 line 2 base - +++++++ Contents of side #3 + +++++++ side #3 line 2 base line 3 c.2 - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends line 5 " ); @@ -294,25 +295,25 @@ fn test_materialize_conflict_three_sides() { &materialize_conflict_string(store, path, &conflict, ConflictMarkerStyle::Git), @r" line 1 - <<<<<<< Conflict 1 of 1 - +++++++ Contents of side #1 + <<<<<<< conflict 1 of 1 + +++++++ side #1 line 2 a.1 line 3 a.2 line 4 base - ------- Contents of base #1 + ------- base #1 line 2 base line 3 base line 4 base - +++++++ Contents of side #2 + +++++++ side #2 line 2 b.1 line 3 base line 4 b.2 - ------- Contents of base #2 + ------- base #2 line 2 base - +++++++ Contents of side #3 + +++++++ side #3 line 2 base line 3 c.2 - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends line 5 " ); @@ -375,19 +376,19 @@ fn test_materialize_conflict_multi_rebase_conflicts() { &materialize_conflict_string(store, path, &conflict, ConflictMarkerStyle::Diff), @r" line 1 - <<<<<<< Conflict 1 of 1 - +++++++ Contents of side #1 + <<<<<<< conflict 1 of 1 + +++++++ side #1 line 2 a.1 line 2 a.2 line 2 a.3 - %%%%%%% Changes from base #1 to side #2 + %%%%%%% side #2 compared with base #1 -line 2 base +line 2 b.1 +line 2 b.2 - %%%%%%% Changes from base #2 to side #3 + %%%%%%% side #3 compared with base #2 -line 2 base +line 2 c.1 - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends line 3 " ); @@ -399,19 +400,19 @@ fn test_materialize_conflict_multi_rebase_conflicts() { &materialize_conflict_string(store, path, &conflict, ConflictMarkerStyle::Diff), @r" line 1 - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base #1 to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% side #1 compared with base #1 -line 2 base +line 2 c.1 - %%%%%%% Changes from base #2 to side #2 + %%%%%%% side #2 compared with base #2 -line 2 base +line 2 b.1 +line 2 b.2 - +++++++ Contents of side #3 + +++++++ side #3 line 2 a.1 line 2 a.2 line 2 a.3 - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends line 3 " ); @@ -423,19 +424,19 @@ fn test_materialize_conflict_multi_rebase_conflicts() { &materialize_conflict_string(store, path, &conflict, ConflictMarkerStyle::Diff), @r" line 1 - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base #1 to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% side #1 compared with base #1 -line 2 base +line 2 c.1 - +++++++ Contents of side #2 + +++++++ side #2 line 2 a.1 line 2 a.2 line 2 a.3 - %%%%%%% Changes from base #2 to side #3 + %%%%%%% side #3 compared with base #2 -line 2 base +line 2 b.1 +line 2 b.2 - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends line 3 " ); @@ -491,25 +492,25 @@ fn test_materialize_parse_roundtrip() { insta::assert_snapshot!( materialized, @r" - <<<<<<< Conflict 1 of 2 - +++++++ Contents of side #1 + <<<<<<< conflict 1 of 2 + +++++++ side #1 line 1 left line 2 left - %%%%%%% Changes from base to side #2 + %%%%%%% side #2 compared with base -line 1 +line 1 right line 2 - >>>>>>> Conflict 1 of 2 ends + >>>>>>> conflict 1 of 2 ends line 3 - <<<<<<< Conflict 2 of 2 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 2 of 2 + %%%%%%% side #1 compared with base line 4 -line 5 +line 5 left - +++++++ Contents of side #2 + +++++++ side #2 line 4 right line 5 right - >>>>>>> Conflict 2 of 2 ends + >>>>>>> conflict 2 of 2 ends " ); @@ -620,12 +621,12 @@ fn test_materialize_conflict_no_newlines_at_eof() { &materialize_conflict_string(store, path, &conflict, ConflictMarkerStyle::Diff); insta::assert_snapshot!(materialized, @r" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 (adds terminating newline) + <<<<<<< conflict 1 of 1 + %%%%%%% side #1 compared with base (adds terminating newline) -base - +++++++ Contents of side #2 (no terminating newline) + +++++++ side #2 (no terminating newline) right - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends " ); // The conflict markers are parsed with the trailing newline, but it is removed @@ -698,12 +699,12 @@ fn test_materialize_conflict_modify_delete() { insta::assert_snapshot!(&materialize_conflict_string(store, path, &conflict, ConflictMarkerStyle::Diff), @r" line 1 line 2 - <<<<<<< Conflict 1 of 1 - +++++++ Contents of side #1 + <<<<<<< conflict 1 of 1 + +++++++ side #1 modified - %%%%%%% Changes from base to side #2 + %%%%%%% side #2 compared with base -line 3 - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends line 4 line 5 " @@ -717,12 +718,12 @@ fn test_materialize_conflict_modify_delete() { insta::assert_snapshot!(&materialize_conflict_string(store, path, &conflict, ConflictMarkerStyle::Diff), @r" line 1 line 2 - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% side #1 compared with base -line 3 - +++++++ Contents of side #2 + +++++++ side #2 modified - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends line 4 line 5 " @@ -734,16 +735,16 @@ fn test_materialize_conflict_modify_delete() { vec![Some(modified_id.clone()), None], ); insta::assert_snapshot!(&materialize_conflict_string(store, path, &conflict, ConflictMarkerStyle::Diff), @r" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% side #1 compared with base line 1 line 2 -line 3 +modified line 4 line 5 - +++++++ Contents of side #2 - >>>>>>> Conflict 1 of 1 ends + +++++++ side #2 + >>>>>>> conflict 1 of 1 ends " ); } @@ -788,18 +789,93 @@ fn test_materialize_conflict_two_forward_diffs() { insta::assert_snapshot!( &materialize_conflict_string(store, path, &conflict, ConflictMarkerStyle::Diff), @r" - <<<<<<< Conflict 1 of 1 - +++++++ Contents of side #1 + <<<<<<< conflict 1 of 1 + +++++++ side #1 A - %%%%%%% Changes from base #1 to side #2 + %%%%%%% side #2 compared with base #1 B - %%%%%%% Changes from base #2 to side #3 + %%%%%%% side #3 compared with base #2 -C +D - %%%%%%% Changes from base #3 to side #4 + %%%%%%% side #4 compared with base #3 -E +C - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends + " + ); +} + +#[test] +fn test_materialize_conflict_with_labels() { + let test_repo = TestRepo::init(); + let store = test_repo.repo.store(); + + let path = repo_path("file"); + let side1 = testutils::write_file(store, path, "side 1\n"); + let base1 = testutils::write_file(store, path, "base 1\n"); + let side2 = testutils::write_file(store, path, "side 2\n"); + + let conflict = Merge::from_vec(vec![Some(side1), Some(base1), Some(side2)]); + let conflict_labels = ConflictLabels::from_vec(vec![ + "commit 1".into(), + "commit 2".into(), + "commit 3".into(), + ]); + insta::assert_snapshot!( + &materialize_conflict_string_with_labels( + store, + path, + &conflict, + &conflict_labels, + ConflictMarkerStyle::Diff, + ), + @r" + <<<<<<< conflict 1 of 1 + %%%%%%% commit 1 compared with commit 2 + -base 1 + +side 1 + +++++++ commit 3 + side 2 + >>>>>>> conflict 1 of 1 ends + " + ); + + insta::assert_snapshot!( + &materialize_conflict_string_with_labels( + store, + path, + &conflict, + &conflict_labels, + ConflictMarkerStyle::Snapshot, + ), + @r" + <<<<<<< conflict 1 of 1 + +++++++ commit 1 + side 1 + ------- commit 2 + base 1 + +++++++ commit 3 + side 2 + >>>>>>> conflict 1 of 1 ends + " + ); + + insta::assert_snapshot!( + &materialize_conflict_string_with_labels( + store, + path, + &conflict, + &conflict_labels, + ConflictMarkerStyle::Git, + ), + @r" + <<<<<<< commit 1 (conflict 1 of 1) + side 1 + ||||||| commit 2 + base 1 + ======= + side 2 + >>>>>>> commit 3 (conflict 1 of 1 ends) " ); } @@ -1558,13 +1634,13 @@ fn test_parse_conflict_mixed_header_styles() { insta::assert_debug_snapshot!( parse_conflict(indoc! {b" line 1 - <<<<<<< Conflict 1 of 1 - +++++++ Contents of side #1 + <<<<<<< conflict 1 of 1 + +++++++ side #1 ======= ignored - ------- Contents of base + ------- base ||||||| ignored - +++++++ Contents of side #2 - >>>>>>> Conflict 1 of 1 ends + +++++++ side #2 + >>>>>>> conflict 1 of 1 ends line 5 "}, 2, @@ -1594,13 +1670,13 @@ fn test_parse_conflict_mixed_header_styles() { insta::assert_debug_snapshot!( parse_conflict(indoc! {b" line 1 - <<<<<<< Side #1 (Conflict 1 of 1) - ||||||| Base + <<<<<<< side #1 (conflict 1 of 1) + ||||||| base ------- ignored %%%%%%% ignored ======= +++++++ ignored - >>>>>>> Side #2 (Conflict 1 of 1 ends) + >>>>>>> side #2 (conflict 1 of 1 ends) line 5 "}, 2, @@ -1757,21 +1833,21 @@ fn test_update_conflict_from_content_simplified_conflict() { insta::assert_snapshot!( materialized, @r" - <<<<<<< Conflict 1 of 2 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 2 + %%%%%%% side #1 compared with base -line 1 +left 1 - +++++++ Contents of side #2 + +++++++ side #2 right 1 - >>>>>>> Conflict 1 of 2 ends + >>>>>>> conflict 1 of 2 ends line 2 - <<<<<<< Conflict 2 of 2 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 2 of 2 + %%%%%%% side #1 compared with base -line 3 +left 3 - +++++++ Contents of side #2 + +++++++ side #2 right 3 - >>>>>>> Conflict 2 of 2 ends + >>>>>>> conflict 2 of 2 ends " ); assert_eq!(parse(materialized.as_bytes()), conflict); @@ -1865,23 +1941,23 @@ fn test_update_conflict_from_content_with_long_markers() { let materialized = materialize_conflict_string(store, path, &conflict, ConflictMarkerStyle::Snapshot); insta::assert_snapshot!(materialized, @r" - <<<<<<<<<<<<<<<< Conflict 1 of 2 - ++++++++++++++++ Contents of side #1 + <<<<<<<<<<<<<<<< conflict 1 of 2 + ++++++++++++++++ side #1 <<<< left 1 - ---------------- Contents of base + ---------------- base line 1 - ++++++++++++++++ Contents of side #2 + ++++++++++++++++ side #2 >>>>>>> right 1 - >>>>>>>>>>>>>>>> Conflict 1 of 2 ends + >>>>>>>>>>>>>>>> conflict 1 of 2 ends line 2 - <<<<<<<<<<<<<<<< Conflict 2 of 2 - ++++++++++++++++ Contents of side #1 + <<<<<<<<<<<<<<<< conflict 2 of 2 + ++++++++++++++++ side #1 <<<<<<<<<<<< left 3 - ---------------- Contents of base + ---------------- base line 3 - ++++++++++++++++ Contents of side #2 + ++++++++++++++++ side #2 >>>>>>>>>>>> right 3 - >>>>>>>>>>>>>>>> Conflict 2 of 2 ends + >>>>>>>>>>>>>>>> conflict 2 of 2 ends " ); @@ -1967,14 +2043,14 @@ fn test_update_conflict_from_content_with_long_markers() { insta::assert_snapshot!( materialize_conflict_string(store, path, &new_conflict, ConflictMarkerStyle::Snapshot), @r" - <<<<<<<<<<< Conflict 1 of 1 - +++++++++++ Contents of side #1 + <<<<<<<<<<< conflict 1 of 1 + +++++++++++ side #1 <<<< left 1 - ----------- Contents of base + ----------- base line 1 - +++++++++++ Contents of side #2 + +++++++++++ side #2 >>>>>>> right 1 - >>>>>>>>>>> Conflict 1 of 1 ends + >>>>>>>>>>> conflict 1 of 1 ends line 2 line 3 " @@ -2002,22 +2078,22 @@ fn test_update_conflict_from_content_no_eol() { insta::assert_snapshot!(materialized, @r" line 1 - <<<<<<< Conflict 1 of 2 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 2 + %%%%%%% side #1 compared with base -line 2 +line 2 left - +++++++ Contents of side #2 + +++++++ side #2 line 2 right - >>>>>>> Conflict 1 of 2 ends + >>>>>>> conflict 1 of 2 ends line 3 - <<<<<<< Conflict 2 of 2 - +++++++ Contents of side #1 + <<<<<<< conflict 2 of 2 + +++++++ side #1 base left - %%%%%%% Changes from base to side #2 (no terminating newline) + %%%%%%% side #2 compared with base (no terminating newline) -base +right - >>>>>>> Conflict 2 of 2 ends + >>>>>>> conflict 2 of 2 ends " ); assert_eq!( @@ -2038,24 +2114,24 @@ fn test_update_conflict_from_content_no_eol() { insta::assert_snapshot!(materialized, @r" line 1 - <<<<<<< Conflict 1 of 2 - +++++++ Contents of side #1 + <<<<<<< conflict 1 of 2 + +++++++ side #1 line 2 left - ------- Contents of base + ------- base line 2 - +++++++ Contents of side #2 + +++++++ side #2 line 2 right - >>>>>>> Conflict 1 of 2 ends + >>>>>>> conflict 1 of 2 ends line 3 - <<<<<<< Conflict 2 of 2 - +++++++ Contents of side #1 + <<<<<<< conflict 2 of 2 + +++++++ side #1 base left - ------- Contents of base (no terminating newline) + ------- base (no terminating newline) base - +++++++ Contents of side #2 (no terminating newline) + +++++++ side #2 (no terminating newline) right - >>>>>>> Conflict 2 of 2 ends + >>>>>>> conflict 2 of 2 ends " ); assert_eq!( @@ -2076,22 +2152,22 @@ fn test_update_conflict_from_content_no_eol() { insta::assert_snapshot!(materialized, @r" line 1 - <<<<<<< Side #1 (Conflict 1 of 2) + <<<<<<< side #1 (conflict 1 of 2) line 2 left - ||||||| Base + ||||||| base line 2 ======= line 2 right - >>>>>>> Side #2 (Conflict 1 of 2 ends) + >>>>>>> side #2 (conflict 1 of 2 ends) line 3 - <<<<<<< Side #1 (Conflict 2 of 2) + <<<<<<< side #1 (conflict 2 of 2) base left - ||||||| Base + ||||||| base base ======= right - >>>>>>> Side #2 (Conflict 2 of 2 ends) + >>>>>>> side #2 (conflict 2 of 2 ends) " ); assert_eq!( @@ -2145,26 +2221,26 @@ fn test_update_conflict_from_content_no_eol_in_diff_hunk() { &materialize_conflict_string(store, path, &conflict, ConflictMarkerStyle::Diff); insta::assert_snapshot!(materialized, @r" - <<<<<<< Conflict 1 of 1 - +++++++ Contents of side #1 + <<<<<<< conflict 1 of 1 + +++++++ side #1 side - %%%%%%% Changes from base #1 to side #2 (adds terminating newline) + %%%%%%% side #2 compared with base #1 (adds terminating newline) add newline -line +line - %%%%%%% Changes from base #2 to side #3 (removes terminating newline) + %%%%%%% side #3 compared with base #2 (removes terminating newline) remove newline -line +line - %%%%%%% Changes from base #3 to side #4 (no terminating newline) + %%%%%%% side #4 compared with base #3 (no terminating newline) no newline -line 1 +line 2 - %%%%%%% Changes from base #4 to side #5 + %%%%%%% side #5 compared with base #4 with newline -line 1 +line 2 - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends " ); assert_eq!( @@ -2201,12 +2277,12 @@ fn test_update_conflict_from_content_only_no_eol_change() { insta::assert_snapshot!(materialized, @r" line 1 - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 (removes terminating newline) + <<<<<<< conflict 1 of 1 + %%%%%%% side #1 compared with base (removes terminating newline) +line 2 - +++++++ Contents of side #2 + +++++++ side #2 line 2 - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends " ); assert_eq!( @@ -2279,21 +2355,21 @@ fn test_update_from_content_malformed_conflict() { materialize_conflict_string(store, path, &conflict, ConflictMarkerStyle::Diff); insta::assert_snapshot!(materialized, @r" line 1 - <<<<<<< Conflict 1 of 2 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 2 + %%%%%%% side #1 compared with base -line 2 +line 2 left - +++++++ Contents of side #2 + +++++++ side #2 line 2 right - >>>>>>> Conflict 1 of 2 ends + >>>>>>> conflict 1 of 2 ends line 3 - <<<<<<< Conflict 2 of 2 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 2 of 2 + %%%%%%% side #1 compared with base -line 4 +line 4 left - +++++++ Contents of side #2 + +++++++ side #2 line 4 right - >>>>>>> Conflict 2 of 2 ends + >>>>>>> conflict 2 of 2 ends line 5 " ); @@ -2383,6 +2459,22 @@ fn materialize_conflict_string( path: &RepoPath, conflict: &Merge>, marker_style: ConflictMarkerStyle, +) -> String { + materialize_conflict_string_with_labels( + store, + path, + conflict, + &ConflictLabels::unlabeled(), + marker_style, + ) +} + +fn materialize_conflict_string_with_labels( + store: &Store, + path: &RepoPath, + conflict: &Merge>, + conflict_labels: &ConflictLabels, + marker_style: ConflictMarkerStyle, ) -> String { let contents = extract_as_single_hunk(conflict, store, path) .block_on() @@ -2395,5 +2487,8 @@ fn materialize_conflict_string( same_change: SameChange::Accept, }, }; - String::from_utf8(materialize_merge_result_to_bytes(&contents, &options).into()).unwrap() + String::from_utf8( + materialize_merge_result_to_bytes(&contents, conflict_labels, &options).into(), + ) + .unwrap() } diff --git a/lib/tests/test_git_backend.rs b/lib/tests/test_git_backend.rs index ae0f01f710a..18945d21a14 100644 --- a/lib/tests/test_git_backend.rs +++ b/lib/tests/test_git_backend.rs @@ -23,8 +23,11 @@ use futures::executor::block_on_stream; use jj_lib::backend::CommitId; use jj_lib::backend::CopyRecord; use jj_lib::commit::Commit; +use jj_lib::conflict_labels::ConflictLabels; use jj_lib::git_backend::GitBackend; use jj_lib::git_backend::JJ_TREES_COMMIT_HEADER; +use jj_lib::merge::Merge; +use jj_lib::merged_tree::MergedTree; use jj_lib::object_id::ObjectId as _; use jj_lib::repo::ReadonlyRepo; use jj_lib::repo::Repo as _; @@ -371,3 +374,25 @@ fn test_jj_trees_header_with_one_tree() { ) "#); } + +#[test] +fn test_conflict_headers_roundtrip() { + let test_repo = TestRepo::init_with_backend(TestRepoBackend::Git); + let repo = test_repo.repo; + + let tree_1 = create_single_tree(&repo, &[(repo_path("file"), "aaa")]); + let tree_2 = create_single_tree(&repo, &[(repo_path("file"), "bbb")]); + let tree_3 = create_single_tree(&repo, &[(repo_path("file"), "ccc")]); + + let merged_tree = MergedTree::new( + Merge::from_vec(vec![tree_1, tree_2, tree_3]), + ConflictLabels::from_vec(vec!["side 1".into(), "base".into(), "side 2".into()]), + ); + + // Create a commit with the conflicted tree. + let commit = commit_with_tree(repo.store(), merged_tree.id()); + // Clear cached commit to ensure it is re-read. + repo.store().clear_caches(); + // Conflict trees and labels should be preserved on read. + assert_eq!(repo.store().get_commit(commit.id()).unwrap(), commit); +} diff --git a/lib/tests/test_id_prefix.rs b/lib/tests/test_id_prefix.rs index f79a91b5da4..9cc1795b905 100644 --- a/lib/tests/test_id_prefix.rs +++ b/lib/tests/test_id_prefix.rs @@ -549,7 +549,7 @@ fn test_id_prefix_shadowed_by_ref() { let commit_id_sym = commit.id().to_string(); let change_id_sym = commit.change_id().to_string(); - insta::assert_snapshot!(commit_id_sym, @"38b5c5aebe81a8441470"); + insta::assert_snapshot!(commit_id_sym, @"673f04bd4087fba5f703"); insta::assert_snapshot!(change_id_sym, @"sryyqqkqmuumyrlruupspprvnulvovzm"); let context = IdPrefixContext::default(); diff --git a/lib/tests/test_local_working_copy.rs b/lib/tests/test_local_working_copy.rs index 376104b9a61..00e33b43ecd 100644 --- a/lib/tests/test_local_working_copy.rs +++ b/lib/tests/test_local_working_copy.rs @@ -422,7 +422,7 @@ fn test_conflict_subdirectory() { let tree1 = create_tree(repo, &[(path, "0")]); let commit1 = commit_with_tree(repo.store(), tree1.id()); let tree2 = create_tree(repo, &[(path, "1")]); - let merged_tree = tree1.merge(empty_tree, tree2).block_on().unwrap(); + let merged_tree = tree1.merge_unlabeled(empty_tree, tree2).block_on().unwrap(); let merged_commit = commit_with_tree(repo.store(), merged_tree.id()); let repo = &test_workspace.repo; let ws = &mut test_workspace.workspace; @@ -856,10 +856,10 @@ fn test_materialize_snapshot_conflicted_files() { let base2_tree = create_tree(repo, &[(file1_path, "b\n"), (file2_path, "3\n")]); let side3_tree = create_tree(repo, &[(file1_path, "c\n"), (file2_path, "3\n")]); let merged_tree = side1_tree - .merge(base1_tree, side2_tree) + .merge_unlabeled(base1_tree, side2_tree) .block_on() .unwrap() - .merge(base2_tree, side3_tree) + .merge_unlabeled(base2_tree, side3_tree) .block_on() .unwrap(); let commit = commit_with_tree(repo.store(), merged_tree.id()); @@ -884,24 +884,24 @@ fn test_materialize_snapshot_conflicted_files() { insta::assert_snapshot!( std::fs::read_to_string(file1_path.to_fs_path_unchecked(&workspace_root)).ok().unwrap(), @r" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% side #1 compared with base -b +a - +++++++ Contents of side #2 + +++++++ side #2 c - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends "); insta::assert_snapshot!( std::fs::read_to_string(file2_path.to_fs_path_unchecked(&workspace_root)).ok().unwrap(), @r" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% side #1 compared with base -2 +1 - +++++++ Contents of side #2 + +++++++ side #2 4 - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends "); // Editing a conflicted file should correctly propagate updates to each of @@ -910,13 +910,13 @@ fn test_materialize_snapshot_conflicted_files() { &workspace_root, file1_path, indoc! {" - <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + <<<<<<< conflict 1 of 1 + %%%%%%% side #1 compared with base -b_edited +a_edited - +++++++ Contents of side #2 + +++++++ side #2 c_edited - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends "}, ); @@ -987,7 +987,10 @@ fn test_materialize_snapshot_unchanged_conflicts() { let base_tree = create_tree(repo, &[(file_path, base_content)]); let left_tree = create_tree(repo, &[(file_path, left_content)]); let right_tree = create_tree(repo, &[(file_path, right_content)]); - let merged_tree = left_tree.merge(base_tree, right_tree).block_on().unwrap(); + let merged_tree = left_tree + .merge_unlabeled(base_tree, right_tree) + .block_on() + .unwrap(); let commit = commit_with_tree(repo.store(), merged_tree.id()); test_workspace @@ -1001,15 +1004,15 @@ fn test_materialize_snapshot_unchanged_conflicts() { insta::assert_snapshot!(materialized_content, @r" line 1 line 2 - <<<<<<< Conflict 1 of 1 - +++++++ Contents of side #1 + <<<<<<< conflict 1 of 1 + +++++++ side #1 left 3.1 left 3.2 left 3.3 - %%%%%%% Changes from base to side #2 + %%%%%%% side #2 compared with base -line 3 +right 3.1 - >>>>>>> Conflict 1 of 1 ends + >>>>>>> conflict 1 of 1 ends line 4 "); diff --git a/lib/tests/test_merge_trees.rs b/lib/tests/test_merge_trees.rs index 985a238c0bc..e6fa2129d12 100644 --- a/lib/tests/test_merge_trees.rs +++ b/lib/tests/test_merge_trees.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use jj_lib::backend::MergedTreeId; use jj_lib::backend::TreeValue; use jj_lib::config::ConfigLayer; use jj_lib::config::ConfigSource; @@ -256,7 +255,7 @@ fn test_rebase_on_lossy_merge(same_change: SameChange) { tree_3.id().into_merge(), ]) .flatten(); - assert_eq!(*commit_d2.tree_id(), MergedTreeId::new(expected_tree_id)); + assert_eq!(*commit_d2.tree_id().as_merge(), expected_tree_id); } } } diff --git a/lib/tests/test_merged_tree.rs b/lib/tests/test_merged_tree.rs index 14e6f6245e5..5dad2eee53c 100644 --- a/lib/tests/test_merged_tree.rs +++ b/lib/tests/test_merged_tree.rs @@ -19,6 +19,7 @@ use jj_lib::backend::CopyRecord; use jj_lib::backend::FileId; use jj_lib::backend::MergedTreeId; use jj_lib::backend::TreeValue; +use jj_lib::conflict_labels::ConflictLabels; use jj_lib::copies::CopiesTreeDiffEntryPath; use jj_lib::copies::CopyOperation; use jj_lib::copies::CopyRecords; @@ -55,13 +56,13 @@ fn diff_entry_tuple(diff: TreeDiffEntry) -> (RepoPathBuf, (MergedTreeValue, Merg fn diff_stream_equals_iter(tree1: &MergedTree, tree2: &MergedTree, matcher: &dyn Matcher) { let trees1 = tree1.as_merge(); let trees2 = tree2.as_merge(); - let iter_diff: Vec<_> = TreeDiffIterator::new(trees1, trees2, matcher) + let iter_diff: Vec<_> = TreeDiffIterator::new(trees1, trees2, matcher, false) .map(|diff| (diff.path, diff.values.unwrap())) .collect(); let max_concurrent_reads = 10; tree1.store().clear_caches(); let stream_diff: Vec<_> = - TreeDiffStreamImpl::new(trees1, trees2, matcher, max_concurrent_reads) + TreeDiffStreamImpl::new(trees1, trees2, matcher, max_concurrent_reads, false) .map(|diff| (diff.path, diff.values.unwrap())) .collect() .block_on(); @@ -81,7 +82,7 @@ fn test_merged_tree_builder_resolves_conflict() { let tree2 = create_single_tree(repo, &[(path1, "bar")]); let tree3 = create_single_tree(repo, &[(path1, "bar")]); - let base_tree_id = MergedTreeId::new(Merge::from_removes_adds( + let base_tree_id = MergedTreeId::unlabeled(Merge::from_removes_adds( [tree1.id().clone()], [tree2.id().clone(), tree3.id().clone()], )); @@ -130,7 +131,7 @@ fn test_path_value_and_entries() { (file_dir_conflict_sub_path, "1"), ], ); - let merged_tree = MergedTree::new(Merge::from_removes_adds( + let merged_tree = MergedTree::unlabeled(Merge::from_removes_adds( vec![tree1.clone()], vec![tree2.clone(), tree3.clone()], )); @@ -279,7 +280,10 @@ fn test_resolve_success() { ], ); - let tree = MergedTree::new(Merge::from_removes_adds(vec![base1], vec![side1, side2])); + let tree = MergedTree::new( + Merge::from_vec(vec![side1, base1, side2]), + ConflictLabels::from_vec(vec!["left".into(), "base".into(), "right".into()]), + ); let resolved_tree = tree.resolve().block_on().unwrap(); assert!(resolved_tree.as_merge().is_resolved()); assert_eq!( @@ -303,7 +307,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::from_removes_adds(vec![base1], vec![side1, side2])); + let tree = MergedTree::unlabeled(Merge::from_removes_adds(vec![base1], vec![side1, side2])); let resolved = tree.resolve().block_on().unwrap(); assert_eq!(resolved.id(), store.empty_merged_tree_id()); } @@ -317,24 +321,39 @@ fn test_resolve_with_conflict() { // cannot) let trivial_path = repo_path("dir1/trivial"); let conflict_path = repo_path("dir2/file_conflict"); - let base1 = create_single_tree(repo, &[(trivial_path, "base1"), (conflict_path, "base1")]); - let side1 = create_single_tree(repo, &[(trivial_path, "side1"), (conflict_path, "side1")]); - let side2 = create_single_tree(repo, &[(trivial_path, "base1"), (conflict_path, "side2")]); - let expected_base1 = - create_single_tree(repo, &[(trivial_path, "side1"), (conflict_path, "base1")]); + + // We start with a 3-sided conflict: + let side1 = create_single_tree(repo, &[(trivial_path, "side1"), (conflict_path, "base")]); + let base1 = create_single_tree(repo, &[(trivial_path, "base"), (conflict_path, "base")]); + let side2 = create_single_tree(repo, &[(trivial_path, "base"), (conflict_path, "side2")]); + let base2 = create_single_tree(repo, &[(trivial_path, "base"), (conflict_path, "base")]); + let side3 = create_single_tree(repo, &[(trivial_path, "base"), (conflict_path, "side3")]); + + // This should be reduced to a 2-sided conflict after "trivial" is resolved: let expected_side1 = - create_single_tree(repo, &[(trivial_path, "side1"), (conflict_path, "side1")]); - let expected_side2 = create_single_tree(repo, &[(trivial_path, "side1"), (conflict_path, "side2")]); - - let tree = MergedTree::new(Merge::from_removes_adds(vec![base1], vec![side1, side2])); + let expected_base1 = + create_single_tree(repo, &[(trivial_path, "side1"), (conflict_path, "base")]); + let expected_side2 = + create_single_tree(repo, &[(trivial_path, "side1"), (conflict_path, "side3")]); + + let tree = MergedTree::new( + Merge::from_vec(vec![side1, base1, side2, base2, side3]), + ConflictLabels::from_vec(vec![ + "side 1".into(), + "base 1".into(), + "side 2".into(), + "base 2".into(), + "side 3".into(), + ]), + ); let resolved_tree = tree.resolve().block_on().unwrap(); assert_eq!( resolved_tree, - MergedTree::new(Merge::from_removes_adds( - vec![expected_base1], - vec![expected_side1, expected_side2] - )) + MergedTree::new( + Merge::from_vec(vec![expected_side1, expected_base1, expected_side2]), + ConflictLabels::from_vec(vec!["side 2".into(), "base 2".into(), "side 3".into()]), + ) ); } @@ -350,7 +369,10 @@ fn test_resolve_with_conflict_containing_empty_subtree() { let side1 = create_single_tree(repo, &[(conflict_path, "side1")]); let side2 = create_single_tree(repo, &[]); - let tree = MergedTree::new(Merge::from_removes_adds(vec![base1], vec![side1, side2])); + let tree = MergedTree::new( + Merge::from_vec(vec![side1, base1, side2]), + ConflictLabels::from_vec(vec!["left".into(), "base".into(), "right".into()]), + ); let resolved_tree = tree.clone().resolve().block_on().unwrap(); assert_eq!(resolved_tree, tree); } @@ -422,7 +444,7 @@ fn test_conflict_iterator() { ], ); - let tree = MergedTree::new(Merge::from_removes_adds( + let tree = MergedTree::unlabeled(Merge::from_removes_adds( vec![base1.clone()], vec![side1.clone(), side2.clone()], )); @@ -515,7 +537,7 @@ fn test_conflict_iterator_higher_arity() { &[(two_sided_path, "side3"), (three_sided_path, "side3")], ); - let tree = MergedTree::new(Merge::from_removes_adds( + let tree = MergedTree::unlabeled(Merge::from_removes_adds( vec![base1.clone(), base2.clone()], vec![side1.clone(), side2.clone(), side3.clone()], )); @@ -572,8 +594,8 @@ fn test_diff_resolved() { (added_path, "after"), ], ); - let before_merged = MergedTree::new(Merge::resolved(before.clone())); - let after_merged = MergedTree::new(Merge::resolved(after.clone())); + let before_merged = MergedTree::resolved(before.clone()); + let after_merged = MergedTree::resolved(after.clone()); let diff: Vec<_> = before_merged .diff_stream(&after_merged, &EverythingMatcher) @@ -658,8 +680,8 @@ fn test_diff_copy_tracing() { (added_path, "after"), ], ); - let before_merged = MergedTree::new(Merge::resolved(before.clone())); - let after_merged = MergedTree::new(Merge::resolved(after.clone())); + let before_merged = MergedTree::resolved(before.clone()); + let after_merged = MergedTree::resolved(after.clone()); let copy_records = create_copy_records(&[(removed_path, added_path), (modified_path, copied_path)]); @@ -850,11 +872,11 @@ fn test_diff_conflicted() { (path4, "right-side2"), ], ); - let left_merged = MergedTree::new(Merge::from_removes_adds( + let left_merged = MergedTree::unlabeled(Merge::from_removes_adds( vec![left_base.clone()], vec![left_side1.clone(), left_side2.clone()], )); - let right_merged = MergedTree::new(Merge::from_removes_adds( + let right_merged = MergedTree::unlabeled(Merge::from_removes_adds( vec![right_base.clone()], vec![right_side1.clone(), right_side2.clone()], )); @@ -899,6 +921,44 @@ fn test_diff_conflicted() { .collect_vec(); assert_eq!(actual_diff, expected_diff); diff_stream_equals_iter(&right_merged, &left_merged, &EverythingMatcher); + // Test diff stream for filesystem without unchanged conflicts + let actual_diff: Vec<_> = left_merged + .diff_stream_for_file_system(&right_merged, &EverythingMatcher, false) + .map(diff_entry_tuple) + .collect() + .block_on(); + let expected_diff = [path2, path3, path4] + .iter() + .map(|&path| { + ( + path.to_owned(), + ( + left_merged.path_value(path).unwrap(), + right_merged.path_value(path).unwrap(), + ), + ) + }) + .collect_vec(); + assert_eq!(actual_diff, expected_diff); + // Test diff stream for filesystem with unchanged conflicts + let actual_diff: Vec<_> = left_merged + .diff_stream_for_file_system(&right_merged, &EverythingMatcher, true) + .map(diff_entry_tuple) + .collect() + .block_on(); + let expected_diff = [path1, path2, path3, path4] + .iter() + .map(|&path| { + ( + path.to_owned(), + ( + left_merged.path_value(path).unwrap(), + right_merged.path_value(path).unwrap(), + ), + ) + }) + .collect_vec(); + assert_eq!(actual_diff, expected_diff); } #[test] @@ -985,11 +1045,11 @@ fn test_diff_dir_file() { (&path6.join(file), "right"), ], ); - let left_merged = MergedTree::new(Merge::from_removes_adds( + let left_merged = MergedTree::unlabeled(Merge::from_removes_adds( vec![left_base], vec![left_side1, left_side2], )); - let right_merged = MergedTree::new(Merge::from_removes_adds( + let right_merged = MergedTree::unlabeled(Merge::from_removes_adds( vec![right_base], vec![right_side1, right_side2], )); @@ -1169,13 +1229,13 @@ fn test_merge_simple() { let side1 = create_single_tree(repo, &[(path1, "side1"), (path2, "base")]); let side2 = create_single_tree(repo, &[(path1, "base"), (path2, "side2")]); let expected = create_single_tree(repo, &[(path1, "side1"), (path2, "side2")]); - 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::resolved(expected)); + let base1_merged = MergedTree::resolved(base1); + let side1_merged = MergedTree::resolved(side1); + let side2_merged = MergedTree::resolved(side2); + let expected_merged = MergedTree::resolved(expected); let merged = side1_merged - .merge(base1_merged, side2_merged) + .merge_unlabeled(base1_merged, side2_merged) .block_on() .unwrap(); assert_eq!(merged, expected_merged); @@ -1196,16 +1256,16 @@ fn test_merge_partial_resolution() { let expected_base1 = create_single_tree(repo, &[(path1, "side1"), (path2, "base")]); let expected_side1 = create_single_tree(repo, &[(path1, "side1"), (path2, "side1")]); let expected_side2 = create_single_tree(repo, &[(path1, "side1"), (path2, "side2")]); - 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::from_removes_adds( + let base1_merged = MergedTree::resolved(base1); + let side1_merged = MergedTree::resolved(side1); + let side2_merged = MergedTree::resolved(side2); + let expected_merged = MergedTree::unlabeled(Merge::from_removes_adds( vec![expected_base1], vec![expected_side1, expected_side2], )); let merged = side1_merged - .merge(base1_merged, side2_merged) + .merge_unlabeled(base1_merged, side2_merged) .block_on() .unwrap(); assert_eq!(merged, expected_merged); @@ -1225,22 +1285,22 @@ 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::from_removes_adds( + let base1_merged = MergedTree::unlabeled(Merge::from_removes_adds( vec![tree1.clone()], vec![tree2.clone(), tree3.clone()], )); - let side1_merged = MergedTree::new(Merge::from_removes_adds( + let side1_merged = MergedTree::unlabeled(Merge::from_removes_adds( vec![tree1.clone()], vec![tree4.clone(), tree2.clone()], )); - let side2_merged = MergedTree::new(Merge::from_removes_adds( + let side2_merged = MergedTree::unlabeled(Merge::from_removes_adds( vec![tree4.clone()], vec![tree5.clone(), tree3.clone()], )); - let expected_merged = MergedTree::new(Merge::resolved(expected)); + let expected_merged = MergedTree::resolved(expected); let merged = side1_merged - .merge(base1_merged, side2_merged) + .merge_unlabeled(base1_merged, side2_merged) .block_on() .unwrap(); assert_eq!(merged, expected_merged); @@ -1257,29 +1317,98 @@ fn test_merge_simplify_result() { // The conflict in path1 cannot be resolved, but the conflict in path2 can. let path1 = repo_path("dir1/file"); let path2 = repo_path("dir2/file"); - let tree1 = create_single_tree(repo, &[(path1, "1"), (path2, "1")]); - let tree2 = create_single_tree(repo, &[(path1, "2"), (path2, "2")]); - let tree3 = create_single_tree(repo, &[(path1, "3"), (path2, "3")]); - let tree4 = create_single_tree(repo, &[(path1, "4"), (path2, "2")]); - let tree5 = create_single_tree(repo, &[(path1, "4"), (path2, "1")]); - let expected_base1 = create_single_tree(repo, &[(path1, "1"), (path2, "3")]); + let side1_left = create_single_tree(repo, &[(path1, "2"), (path2, "2")]); + let side1_base = create_single_tree(repo, &[(path1, "1"), (path2, "1")]); + let side1_right = create_single_tree(repo, &[(path1, "3"), (path2, "3")]); + let base1 = create_single_tree(repo, &[(path1, "4"), (path2, "2")]); + let side2 = create_single_tree(repo, &[(path1, "4"), (path2, "1")]); let expected_side1 = create_single_tree(repo, &[(path1, "2"), (path2, "3")]); + let expected_base1 = create_single_tree(repo, &[(path1, "1"), (path2, "3")]); let expected_side2 = create_single_tree(repo, &[(path1, "3"), (path2, "3")]); - 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::from_removes_adds( - vec![expected_base1], - vec![expected_side1, expected_side2], - )); + let side1_merged = MergedTree::new( + Merge::from_vec(vec![ + side1_left.clone(), + side1_base.clone(), + side1_right.clone(), + ]), + ConflictLabels::from_vec(vec![ + "side 1 left".into(), + "side 1 base".into(), + "side 1 right".into(), + ]), + ); + let base1_merged = MergedTree::resolved(base1.clone()); + let side2_merged = MergedTree::resolved(side2.clone()); + let expected_merged = MergedTree::new( + Merge::from_vec(vec![expected_side1, expected_base1, expected_side2]), + ConflictLabels::from_vec(vec![ + "side 1 left".into(), + "side 1 base".into(), + "side 1 right".into(), + ]), + ); + + // Although we pass labels here, they don't appear in the final result. The + // "side 1" label is ignored because that side is already conflicted. The "base + // 1" and "side 2" labels are used, but then those sides are removed after + // resolving and simplifying. + let merged = MergedTree::merge(Merge::from_vec(vec![ + (side1_merged, "side 1".into()), + (base1_merged, "base 1".into()), + (side2_merged, "side 2".into()), + ])) + .block_on() + .unwrap(); + assert_eq!(merged, expected_merged); +} - let merged = side1_merged - .merge(base1_merged, side2_merged) - .block_on() - .unwrap(); +/// Test that resolved trees take their labels from `MergeLabels`. +#[test] +fn test_merge_simplify_result_with_resolved_labels() { + let test_repo = TestRepo::init(); + let repo = &test_repo.repo; + + // The conflict in path1 cannot be resolved, but the conflict in path2 can. + let path1 = repo_path("dir1/file"); + let path2 = repo_path("dir2/file"); + let side1 = create_single_tree(repo, &[(path1, "2"), (path2, "2")]); + let base1 = create_single_tree(repo, &[(path1, "1"), (path2, "1")]); + let side2_left = create_single_tree(repo, &[(path1, "3"), (path2, "3")]); + let side2_base = create_single_tree(repo, &[(path1, "4"), (path2, "2")]); + let side2_right = create_single_tree(repo, &[(path1, "4"), (path2, "1")]); + let expected_side1 = create_single_tree(repo, &[(path1, "2"), (path2, "3")]); + let expected_base1 = create_single_tree(repo, &[(path1, "1"), (path2, "3")]); + let expected_side2 = create_single_tree(repo, &[(path1, "3"), (path2, "3")]); + let side1_merged = MergedTree::resolved(side1.clone()); + let base1_merged = MergedTree::resolved(base1.clone()); + let side2_merged = MergedTree::new( + Merge::from_vec(vec![ + side2_left.clone(), + side2_base.clone(), + side2_right.clone(), + ]), + ConflictLabels::from_vec(vec![ + "side 2 left".into(), + "side 2 base".into(), + "side 2 right".into(), + ]), + ); + let expected_merged = MergedTree::new( + Merge::from_vec(vec![expected_side1, expected_base1, expected_side2]), + ConflictLabels::from_vec(vec!["side 1".into(), "base 1".into(), "side 2 left".into()]), + ); + + // Since side 1 and base 1 are resolved, they will use the provided "side 1" and + // "base 1" labels. Since side 2 is conflicted, its existing labels are used + // instead of the provided "side 2" label. Two of the terms from side 2 will be + // removed after resolving and simplifying. + let merged = MergedTree::merge(Merge::from_vec(vec![ + (side1_merged, "side 1".into()), + (base1_merged, "base 1".into()), + (side2_merged, "side 2".into()), + ])) + .block_on() + .unwrap(); assert_eq!(merged, expected_merged); } @@ -1353,7 +1482,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::from_removes_adds( + let parent_merged = MergedTree::unlabeled(Merge::from_removes_adds( vec![parent_base], vec![parent_left, parent_right], )); @@ -1371,7 +1500,7 @@ fn test_merge_simplify_file_conflict() { repo, &[(other_path, "child1"), (conflict_path, &child1_right_text)], ); - let child1_merged = MergedTree::new(Merge::from_removes_adds( + let child1_merged = MergedTree::unlabeled(Merge::from_removes_adds( vec![child1_base], vec![child1_left, child1_right], )); @@ -1388,7 +1517,7 @@ fn test_merge_simplify_file_conflict() { let expected_merged = MergedTree::resolved(expected); let merged = child1_merged - .merge(parent_merged, child2_merged) + .merge_unlabeled(parent_merged, child2_merged) .block_on() .unwrap(); assert_eq!(merged, expected_merged); @@ -1432,7 +1561,7 @@ fn test_merge_simplify_file_conflict_with_absent() { let child2_right = create_single_tree(repo, &[(child2_path, ""), (conflict_path, "0\n2\n")]); let child1_merged = MergedTree::resolved(child1); let parent_merged = MergedTree::resolved(parent); - let child2_merged = MergedTree::new(Merge::from_removes_adds( + let child2_merged = MergedTree::unlabeled(Merge::from_removes_adds( vec![child2_base], vec![child2_left, child2_right], )); @@ -1441,7 +1570,7 @@ fn test_merge_simplify_file_conflict_with_absent() { let expected_merged = MergedTree::resolved(expected); let merged = child1_merged - .merge(parent_merged, child2_merged) + .merge_unlabeled(parent_merged, child2_merged) .block_on() .unwrap(); assert_eq!(merged, expected_merged); diff --git a/lib/tests/test_mut_repo.rs b/lib/tests/test_mut_repo.rs index db925ade6c7..f807c116826 100644 --- a/lib/tests/test_mut_repo.rs +++ b/lib/tests/test_mut_repo.rs @@ -154,7 +154,7 @@ fn test_edit_previous_empty_merge() { let old_parent_tree = old_parent1 .tree() .unwrap() - .merge(empty_tree, old_parent2.tree().unwrap()) + .merge_unlabeled(empty_tree, old_parent2.tree().unwrap()) .block_on() .unwrap(); let old_wc_commit = mut_repo diff --git a/lib/tests/test_revset.rs b/lib/tests/test_revset.rs index 2f06a04ef43..fa38182fa60 100644 --- a/lib/tests/test_revset.rs +++ b/lib/tests/test_revset.rs @@ -4555,7 +4555,7 @@ fn test_evaluate_expression_diff_contains_conflict(indexed: bool) { let commit1 = create_commit(vec![repo.store().root_commit_id().clone()], tree1.id()); let tree2 = create_tree(&repo, &[(file_path, "0\n2\n")]); let tree3 = create_tree(&repo, &[(file_path, "0\n3\n")]); - let tree4 = tree2.merge(tree1, tree3).block_on().unwrap(); + let tree4 = tree2.merge_unlabeled(tree1, tree3).block_on().unwrap(); let commit2 = create_commit(vec![commit1.id().clone()], tree4.id()); assert_eq!( @@ -4667,7 +4667,7 @@ fn test_evaluate_expression_conflict() { let tree3 = create_tree(repo, &[(file_path1, "3"), (file_path2, "1")]); let commit3 = create_commit(vec![commit2.id().clone()], tree3.id()); let tree4 = tree2 - .merge(tree1.clone(), tree3.clone()) + .merge_unlabeled(tree1.clone(), tree3.clone()) .block_on() .unwrap(); let commit4 = create_commit(vec![commit3.id().clone()], tree4.id()); diff --git a/lib/tests/test_revset_optimized.rs b/lib/tests/test_revset_optimized.rs index 29a2d6c794e..9c788b81674 100644 --- a/lib/tests/test_revset_optimized.rs +++ b/lib/tests/test_revset_optimized.rs @@ -196,15 +196,15 @@ fn test_mostly_linear() { insta::assert_snapshot!( commits.iter().map(|c| format!("{:<2} {}\n", c.description(), c.id())).join(""), @r" 00000000000000000000 - 1 0481b93947ec320582da - 2 cf00f20ba8d03cfe27d7 - 3 db1fb816b776ac1257a1 - 4 3fa336059698229e1869 - 5 f1d81483ce728dac9c5c - 6 c0620144c8381147c3eb - 7 f1355db0abd492eb4df4 - 8 2aa8feb562b8426f485f - 9 fee08f51862cdd69739d + 1 9347727a23ac136d026b + 2 5c4534eb232b7932ac27 + 3 10bec2cf4b4a75515fc5 + 4 33eb7f9e9ef8bf8546b4 + 5 d0d3647d53753b0b1c67 + 6 a54f0de3bb7821fcb258 + 7 0b007c84e16879cafd60 + 8 2eac20af222a3aa9608d + 9 2c8000b6ebc655b72b39 "); let commit_ids = commits.iter().map(|c| c.id().clone()).collect_vec(); @@ -250,14 +250,14 @@ fn test_weird_merges() { insta::assert_snapshot!( commits.iter().map(|c| format!("{:<2} {}\n", c.description(), c.id())).join(""), @r" 00000000000000000000 - 1 0481b93947ec320582da - 2 cf00f20ba8d03cfe27d7 - 3 eae7b745114ccd1e7c2b - 4 8ff5f4ecfd6e51f7fb46 - 5 0701d1a6ff427cc5d1c1 - 6 8b2aa399c528813d0bfc - 7 abd7903b1690685bb9c8 - 8 0f916c2bef3fe0aa2f54 + 1 9347727a23ac136d026b + 2 5c4534eb232b7932ac27 + 3 00b1f75480b18e77fc98 + 4 7085f958ae1f042e9c00 + 5 bcc333f855632686364c + 6 d28da0fda414d06342c7 + 7 5067c973b6522d2fe22b + 8 bcd39d54b0a57f8e0bc4 "); let commit_ids = commits.iter().map(|c| c.id().clone()).collect_vec(); @@ -326,15 +326,15 @@ fn test_feature_branches() { insta::assert_snapshot!( commits.iter().map(|c| format!("{:<2} {}\n", c.description(), c.id())).join(""), @r" 00000000000000000000 - 1 0481b93947ec320582da - 2 dfc04cc2cdd8ddb7b55b - 3 eae7b745114ccd1e7c2b - 4 8e272dcf1dfef181cb22 - 5 62bb52a8eb37e1dffcb1 - 6 900d6e697d7e53fe31c5 - 7 74bcf8cf11c54a565c0c - 8 01c9367ecaaa7c0bfc47 - 9 60b2edccd2b633d4b164 + 1 9347727a23ac136d026b + 2 93699472db1201082f52 + 3 00b1f75480b18e77fc98 + 4 46dfe619ac49062d18bf + 5 0fec13c6709efa261757 + 6 778679be2d9356bc5b83 + 7 085e9e6c69d725474f90 + 8 86477d8ee9c71753cf68 + 9 91cb695c790da9ff2a55 "); let commit_ids = commits.iter().map(|c| c.id().clone()).collect_vec(); @@ -396,15 +396,15 @@ fn test_rewritten() { insta::assert_snapshot!( commits.iter().map(|c| format!("{:<2} {}\n", c.description(), c.id())).join(""), @r" 00000000000000000000 - 1 0481b93947ec320582da - 2 cf00f20ba8d03cfe27d7 - 3 0ae48179bdee2dc5cbee - 4 01e8f78ec985350a98f5 - 5 2586f14733ac1c4f2b89 - 2b 4dc5572169ee6230a7fc - 3 9efc19868da6032ea4f1 - 5 ea4b6ce59436a2ccfa67 - 5 31f5e38f8d68c839c6f6 + 1 9347727a23ac136d026b + 2 5c4534eb232b7932ac27 + 3 6595a1e42c74951a4086 + 4 49810d5cb6c34127feb0 + 5 ce2ab1dc315839bd495b + 2b 2d5a81f7fbabc5d4f17d + 3 03bff596bac4c9fc4f18 + 5 2c3d504a53680b4b2a3d + 5 c8098708ea0cd0ad52bd "); let commit_ids = commits.iter().map(|c| c.id().clone()).collect_vec();