Skip to content

Commit b4d526d

Browse files
committed
rewrite: add conflict labels for squashed commits
An example of squashing `ysrnknol` into `rtsqusxu`: ``` <<<<<<< conflict 1 of 1 +++++++ squash destination (rtsqusxu 2768b0b9) updated in destination %%%%%%% squashed commit (ysrnknol 7a20f389) compared with parents of ysrnknol 7a20f389 -base +squashed >>>>>>> conflict 1 of 1 ends ```
1 parent 14ac570 commit b4d526d

File tree

3 files changed

+76
-36
lines changed

3 files changed

+76
-36
lines changed

cli/tests/test_squash_command.rs

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -869,26 +869,26 @@ fn test_squash_from_multiple() {
869869

870870
// Squash a few commits sideways
871871
let output = work_dir.run_jj(["squash", "--from=b", "--from=c", "--into=d"]);
872-
insta::assert_snapshot!(output, @r###"
872+
insta::assert_snapshot!(output, @r"
873873
------- stderr -------
874874
Rebased 2 descendant commits
875-
Working copy (@) now at: kpqxywon 703c6f0c f | (no description set)
876-
Parent commit (@-) : yostqsxw 3d6a1899 e | (no description set)
875+
Working copy (@) now at: kpqxywon 49727075 f | (no description set)
876+
Parent commit (@-) : yostqsxw 2e4142e6 e | (no description set)
877877
New conflicts appeared in 1 commits:
878-
yqosqzyt a3221d7a d | (conflict) (no description set)
878+
yqosqzyt 5ae324ae d | (conflict) (no description set)
879879
Hint: To resolve the conflicts, start by creating a commit on top of
880880
the conflicted commit:
881881
jj new yqosqzyt
882882
Then use `jj resolve`, or edit the conflict markers in the file directly.
883883
Once the conflicts are resolved, you can inspect the result with `jj diff`.
884884
Then run `jj squash` to move the resolution into the conflicted commit.
885885
[EOF]
886-
"###);
886+
");
887887
insta::assert_snapshot!(get_log_output(&work_dir), @r"
888-
@ 703c6f0cae6f f
889-
3d6a18995cae e
888+
@ 49727075f875 f
889+
2e4142e67bae e
890890
├─╮
891-
× │ a3221d7ae02a d
891+
× │ 5ae324aed855 d
892892
├─╯
893893
○ e88768e65e67 a b c
894894
◆ 000000000000 (empty)
@@ -898,13 +898,13 @@ fn test_squash_from_multiple() {
898898
let output = work_dir.run_jj(["file", "show", "-r=d", "file"]);
899899
insta::assert_snapshot!(output, @r"
900900
<<<<<<< conflict 1 of 1
901-
%%%%%%% side #1 compared with base #1
901+
%%%%%%% squash destination (yqosqzyt 8acbb715) compared with parents of kkmpptxz fed4d1a2
902902
-a
903903
+d
904-
%%%%%%% side #2 compared with base #2
904+
%%%%%%% squashed commit (kkmpptxz fed4d1a2) compared with parents of mzvwutvl d7e94ec7
905905
-a
906906
+b
907-
+++++++ side #3
907+
+++++++ squashed commit (mzvwutvl d7e94ec7)
908908
c
909909
>>>>>>> conflict 1 of 1 ends
910910
[EOF]
@@ -1013,29 +1013,29 @@ fn test_squash_from_multiple_partial() {
10131013

10141014
// Partially squash a few commits sideways
10151015
let output = work_dir.run_jj(["squash", "--from=b|c", "--into=d", "file1"]);
1016-
insta::assert_snapshot!(output, @r###"
1016+
insta::assert_snapshot!(output, @r"
10171017
------- stderr -------
10181018
Rebased 2 descendant commits
1019-
Working copy (@) now at: kpqxywon f3ae0274 f | (no description set)
1020-
Parent commit (@-) : yostqsxw 45ad30bd e | (no description set)
1019+
Working copy (@) now at: kpqxywon de844869 f | (no description set)
1020+
Parent commit (@-) : yostqsxw c6dd948e e | (no description set)
10211021
New conflicts appeared in 1 commits:
1022-
yqosqzyt 15efa8c0 d | (conflict) (no description set)
1022+
yqosqzyt 95cc1bb7 d | (conflict) (no description set)
10231023
Hint: To resolve the conflicts, start by creating a commit on top of
10241024
the conflicted commit:
10251025
jj new yqosqzyt
10261026
Then use `jj resolve`, or edit the conflict markers in the file directly.
10271027
Once the conflicts are resolved, you can inspect the result with `jj diff`.
10281028
Then run `jj squash` to move the resolution into the conflicted commit.
10291029
[EOF]
1030-
"###);
1030+
");
10311031
insta::assert_snapshot!(get_log_output(&work_dir), @r"
1032-
@ f3ae0274fb6c f
1033-
45ad30bdccc6 e
1032+
@ de8448696599 f
1033+
c6dd948e8e55 e
10341034
├─┬─╮
10351035
│ │ ○ e9db15b956c4 b
10361036
│ ○ │ 83cbe51db94d c
10371037
│ ├─╯
1038-
× │ 15efa8c069e0 d
1038+
× │ 95cc1bb77ab6 d
10391039
├─╯
10401040
○ 64ea60be8d77 a
10411041
◆ 000000000000 (empty)
@@ -1056,13 +1056,13 @@ fn test_squash_from_multiple_partial() {
10561056
let output = work_dir.run_jj(["file", "show", "-r=d", "file1"]);
10571057
insta::assert_snapshot!(output, @r"
10581058
<<<<<<< conflict 1 of 1
1059-
%%%%%%% side #1 compared with base #1
1059+
%%%%%%% squash destination (yqosqzyt f6812ff8) compared with parents of kkmpptxz f2c9709f
10601060
-a
10611061
+d
1062-
%%%%%%% side #2 compared with base #2
1062+
%%%%%%% squashed commit (selected changes from kkmpptxz f2c9709f) compared with parents of mzvwutvl aa908686
10631063
-a
10641064
+b
1065-
+++++++ side #3
1065+
+++++++ squashed commit (selected changes from mzvwutvl aa908686)
10661066
c
10671067
>>>>>>> conflict 1 of 1 ends
10681068
[EOF]

lib/src/merge.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use std::fmt::Formatter;
2222
use std::fmt::Write as _;
2323
use std::future::Future;
2424
use std::hash::Hash;
25+
use std::iter;
2526
use std::iter::zip;
2627
use std::slice;
2728
use std::sync::Arc;
@@ -196,6 +197,15 @@ impl<T> Merge<T> {
196197
Self { values }
197198
}
198199

200+
/// Creates a `Merge` from a first side and a series of diffs to apply to
201+
/// that side.
202+
pub fn from_diffs(first_side: T, diffs: impl IntoIterator<Item = Diff<T>>) -> Self {
203+
let values = iter::once(first_side)
204+
.chain(diffs.into_iter().flat_map(|diff| [diff.before, diff.after]))
205+
.collect();
206+
Self { values }
207+
}
208+
199209
/// Creates a `Merge` with a single resolved value.
200210
pub const fn resolved(value: T) -> Self {
201211
Self {

lib/src/rewrite.rs

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ use crate::index::Index;
3939
use crate::index::IndexResult;
4040
use crate::matchers::Matcher;
4141
use crate::matchers::Visit;
42+
use crate::merge::Diff;
4243
use crate::merge::Merge;
4344
use crate::merged_tree::MergedTree;
4445
use crate::merged_tree::MergedTreeBuilder;
@@ -1152,6 +1153,20 @@ impl CommitWithSelection {
11521153
pub fn is_empty_selection(&self) -> bool {
11531154
!self.selected_tree.id().has_changes(&self.parent_tree.id())
11541155
}
1156+
1157+
/// Returns conflict labels for the diff represented by this selection.
1158+
pub fn conflict_labels(&self) -> BackendResult<Diff<String>> {
1159+
let commit_label = self.commit.conflict_label();
1160+
let parent_label = format!("parents of {commit_label}");
1161+
if self.is_full_selection() {
1162+
Ok(Diff::new(parent_label, commit_label))
1163+
} else {
1164+
Ok(Diff::new(
1165+
parent_label,
1166+
format!("selected changes from {commit_label}"),
1167+
))
1168+
}
1169+
}
11551170
}
11561171

11571172
/// Resulting commit builder and stats to be returned by [`squash_commits()`].
@@ -1174,6 +1189,7 @@ pub fn squash_commits<'repo>(
11741189
) -> BackendResult<Option<SquashedCommit<'repo>>> {
11751190
struct SourceCommit<'a> {
11761191
commit: &'a CommitWithSelection,
1192+
labels: Diff<String>,
11771193
abandon: bool,
11781194
}
11791195
let mut source_commits = vec![];
@@ -1190,6 +1206,7 @@ pub fn squash_commits<'repo>(
11901206
// squash -r`)? The source tree will be unchanged in that case.
11911207
source_commits.push(SourceCommit {
11921208
commit: source,
1209+
labels: source.conflict_labels()?,
11931210
abandon,
11941211
});
11951212
}
@@ -1206,12 +1223,18 @@ pub fn squash_commits<'repo>(
12061223
} else {
12071224
let source_tree = source.commit.commit.tree()?;
12081225
// Apply the reverse of the selected changes onto the source
1209-
let new_source_tree = source_tree
1210-
.merge_unlabeled(
1226+
let new_source_tree = MergedTree::merge(Merge::from_vec(vec![
1227+
(source_tree, source.commit.commit.conflict_label()),
1228+
(
12111229
source.commit.selected_tree.clone(),
1230+
source.labels.after.clone(),
1231+
),
1232+
(
12121233
source.commit.parent_tree.clone(),
1213-
)
1214-
.block_on()?;
1234+
source.labels.before.clone(),
1235+
),
1236+
]))
1237+
.block_on()?;
12151238
repo.rewrite_commit(&source.commit.commit)
12161239
.set_tree_id(new_source_tree.id().clone())
12171240
.write()?;
@@ -1238,22 +1261,29 @@ pub fn squash_commits<'repo>(
12381261
};
12391262
})?;
12401263
}
1241-
// Apply the selected changes onto the destination
1242-
let mut destination_tree = rewritten_destination.tree()?;
1243-
for source in &source_commits {
1244-
destination_tree = destination_tree
1245-
.merge_unlabeled(
1246-
source.commit.parent_tree.clone(),
1247-
source.commit.selected_tree.clone(),
1248-
)
1249-
.block_on()?;
1250-
}
12511264
let mut predecessors = vec![destination.id().clone()];
12521265
predecessors.extend(
12531266
source_commits
12541267
.iter()
12551268
.map(|source| source.commit.commit.id().clone()),
12561269
);
1270+
// Apply the selected changes onto the destination
1271+
let destination_tree = MergedTree::merge(Merge::from_diffs(
1272+
(
1273+
rewritten_destination.tree()?,
1274+
format!("squash destination ({})", destination.conflict_label()),
1275+
),
1276+
source_commits.into_iter().map(|source| {
1277+
Diff::new(
1278+
(source.commit.parent_tree.clone(), source.labels.before),
1279+
(
1280+
source.commit.selected_tree.clone(),
1281+
format!("squashed commit ({})", source.labels.after),
1282+
),
1283+
)
1284+
}),
1285+
))
1286+
.block_on()?;
12571287

12581288
let commit_builder = repo
12591289
.rewrite_commit(&rewritten_destination)

0 commit comments

Comments
 (0)