From 5fd1b2fd5b2422aa9745b1daad4099d29d97f46e Mon Sep 17 00:00:00 2001 From: Scott Taylor Date: Sat, 18 Oct 2025 09:45:48 -0500 Subject: [PATCH] rewrite: add conflict labels for squashed commits An example of squashing `ysrnknol` into `rtsqusxu`: ``` <<<<<<< conflict 1 of 1 +++++++ rtsqusxu 2768b0b9 "A" (squash destination) updated in destination %%%%%%% diff from: vpxusssl 38d49363 "B" (parents of squashed commit) \\\\\\\ to: ysrnknol 7a20f389 "C" (squashed commit) -base +squashed >>>>>>> conflict 1 of 1 ends ``` Or with only partial changes selected: ``` <<<<<<< conflict 1 of 1 +++++++ rtsqusxu 2768b0b9 "A" (squash destination) updated in destination %%%%%%% diff from: vpxusssl 38d49363 "B" (parents of squashed commit) \\\\\\\ to: selected changes for squash (from ysrnknol 7a20f389 "C") -base +selected changes >>>>>>> conflict 1 of 1 ends ``` --- cli/tests/test_squash_command.rs | 40 +++++++++++++++++-------------- lib/src/rewrite.rs | 41 ++++++++++++++++++-------------- 2 files changed, 45 insertions(+), 36 deletions(-) diff --git a/cli/tests/test_squash_command.rs b/cli/tests/test_squash_command.rs index 852a381d2..837c3e0ef 100644 --- a/cli/tests/test_squash_command.rs +++ b/cli/tests/test_squash_command.rs @@ -903,10 +903,10 @@ fn test_squash_from_multiple() { insta::assert_snapshot!(output, @r" ------- stderr ------- Rebased 2 descendant commits - Working copy (@) now at: kpqxywon 0b695306 f | (no description set) - Parent commit (@-) : yostqsxw ff064d52 e | (no description set) + Working copy (@) now at: kpqxywon eb200347 f | (no description set) + Parent commit (@-) : yostqsxw 9475acea e | (no description set) New conflicts appeared in 1 commits: - yqosqzyt 61130da4 d | (conflict) (no description set) + yqosqzyt 33563014 d | (conflict) (no description set) Hint: To resolve the conflicts, start by creating a commit on top of the conflicted commit: jj new yqosqzyt @@ -916,10 +916,10 @@ fn test_squash_from_multiple() { [EOF] "); insta::assert_snapshot!(get_log_output(&work_dir), @r" - @ 0b6953066ee0 f - ○ ff064d529578 e + @ eb200347027e f + ○ 9475acea2503 e ├─╮ - × │ 61130da4e714 d + × │ 335630141fde d ├─╯ ○ e88768e65e67 a b c ◆ 000000000000 (empty) @@ -929,13 +929,15 @@ fn test_squash_from_multiple() { let output = work_dir.run_jj(["file", "show", "-r=d", "file"]); insta::assert_snapshot!(output, @r" <<<<<<< conflict 1 of 1 - %%%%%%% diff from base #1 to side #1 + %%%%%%% diff from: qpvuntsm e88768e6 (parents of squashed commit) + \\\\\\\ to: yqosqzyt 8acbb715 (squash destination) -a +d - %%%%%%% diff from base #2 to side #2 + %%%%%%% diff from: qpvuntsm e88768e6 (parents of squashed commit) + \\\\\\\ to: kkmpptxz fed4d1a2 (squashed commit) -a +b - +++++++ side #3 + +++++++ mzvwutvl d7e94ec7 (squashed commit) c >>>>>>> conflict 1 of 1 ends [EOF] @@ -1047,10 +1049,10 @@ fn test_squash_from_multiple_partial() { insta::assert_snapshot!(output, @r" ------- stderr ------- Rebased 2 descendant commits - Working copy (@) now at: kpqxywon a724910c f | (no description set) - Parent commit (@-) : yostqsxw 1bc405e1 e | (no description set) + Working copy (@) now at: kpqxywon a8eee959 f | (no description set) + Parent commit (@-) : yostqsxw fac4927e e | (no description set) New conflicts appeared in 1 commits: - yqosqzyt 7ddfe685 d | (conflict) (no description set) + yqosqzyt 22dccf0d d | (conflict) (no description set) Hint: To resolve the conflicts, start by creating a commit on top of the conflicted commit: jj new yqosqzyt @@ -1060,13 +1062,13 @@ fn test_squash_from_multiple_partial() { [EOF] "); insta::assert_snapshot!(get_log_output(&work_dir), @r" - @ a724910cd361 f - ○ 1bc405e12b68 e + @ a8eee9597ba5 f + ○ fac4927e5714 e ├─┬─╮ │ │ ○ e9db15b956c4 b │ ○ │ 83cbe51db94d c │ ├─╯ - × │ 7ddfe6857387 d + × │ 22dccf0db9ab d ├─╯ ○ 64ea60be8d77 a ◆ 000000000000 (empty) @@ -1087,13 +1089,15 @@ fn test_squash_from_multiple_partial() { let output = work_dir.run_jj(["file", "show", "-r=d", "file1"]); insta::assert_snapshot!(output, @r" <<<<<<< conflict 1 of 1 - %%%%%%% diff from base #1 to side #1 + %%%%%%% diff from: qpvuntsm 64ea60be (parents of squashed commit) + \\\\\\\ to: yqosqzyt f6812ff8 (squash destination) -a +d - %%%%%%% diff from base #2 to side #2 + %%%%%%% diff from: qpvuntsm 64ea60be (parents of squashed commit) + \\\\\\\ to: selected changes for squash (from kkmpptxz f2c9709f) -a +b - +++++++ side #3 + +++++++ selected changes for squash (from mzvwutvl aa908686) c >>>>>>> conflict 1 of 1 ends [EOF] diff --git a/lib/src/rewrite.rs b/lib/src/rewrite.rs index cb249397f..10bbbb044 100644 --- a/lib/src/rewrite.rs +++ b/lib/src/rewrite.rs @@ -1174,6 +1174,7 @@ impl CommitWithSelection { &self, parent_tree_label: &str, selected_tree_label: &str, + full_selection_label: &str, ) -> BackendResult> { let parents: Vec<_> = self.commit.parents().try_collect()?; let parent_tree_label = format!( @@ -1183,9 +1184,9 @@ impl CommitWithSelection { let commit_label = self.commit.conflict_label(); let selected_tree_label = if self.is_full_selection() { - format!("{commit_label} ({selected_tree_label})") + format!("{commit_label} ({full_selection_label})") } else { - format!("{selected_tree_label} (selected from {commit_label})") + format!("{selected_tree_label} (from {commit_label})") }; Ok(Diff::new( @@ -1215,6 +1216,7 @@ pub fn squash_commits<'repo>( ) -> BackendResult>> { struct SourceCommit<'a> { commit: &'a CommitWithSelection, + diff: Diff<(MergedTree, String)>, abandon: bool, } let mut source_commits = vec![]; @@ -1231,6 +1233,11 @@ pub fn squash_commits<'repo>( // squash -r`)? The source tree will be unchanged in that case. source_commits.push(SourceCommit { commit: source, + diff: source.diff_with_labels( + "parents of squashed commit", + "selected changes for squash", + "squashed commit", + )?, abandon, }); } @@ -1247,12 +1254,11 @@ 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_unlabeled( - source.commit.selected_tree.clone(), - source.commit.parent_tree.clone(), - ) - .block_on()?; + let new_source_tree = MergedTree::merge(Merge::from_diffs( + (source_tree, source.commit.commit.conflict_label()), + [source.diff.clone().invert()], + )) + .block_on()?; repo.rewrite_commit(&source.commit.commit) .set_tree(new_source_tree) .write()?; @@ -1282,22 +1288,21 @@ 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_unlabeled( - 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| source.diff), + )) + .block_on()?; let commit_builder = repo .rewrite_commit(&rewritten_destination)