Skip to content

Commit 855e4d6

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 ac57db1 commit 855e4d6

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
@@ -872,26 +872,26 @@ fn test_squash_from_multiple() {
872872

873873
// Squash a few commits sideways
874874
let output = work_dir.run_jj(["squash", "--from=b", "--from=c", "--into=d"]);
875-
insta::assert_snapshot!(output, @r###"
875+
insta::assert_snapshot!(output, @r"
876876
------- stderr -------
877877
Rebased 2 descendant commits
878-
Working copy (@) now at: kpqxywon 0b695306 f | (no description set)
879-
Parent commit (@-) : yostqsxw ff064d52 e | (no description set)
878+
Working copy (@) now at: kpqxywon 5c538630 f | (no description set)
879+
Parent commit (@-) : yostqsxw fd2940e4 e | (no description set)
880880
New conflicts appeared in 1 commits:
881-
yqosqzyt 61130da4 d | (conflict) (no description set)
881+
yqosqzyt c31cb991 d | (conflict) (no description set)
882882
Hint: To resolve the conflicts, start by creating a commit on top of
883883
the conflicted commit:
884884
jj new yqosqzyt
885885
Then use `jj resolve`, or edit the conflict markers in the file directly.
886886
Once the conflicts are resolved, you can inspect the result with `jj diff`.
887887
Then run `jj squash` to move the resolution into the conflicted commit.
888888
[EOF]
889-
"###);
889+
");
890890
insta::assert_snapshot!(get_log_output(&work_dir), @r"
891-
@ 0b6953066ee0 f
892-
ff064d529578 e
891+
@ 5c5386309539 f
892+
fd2940e4f9c1 e
893893
├─╮
894-
× │ 61130da4e714 d
894+
× │ c31cb991c9be d
895895
├─╯
896896
○ e88768e65e67 a b c
897897
◆ 000000000000 (empty)
@@ -901,13 +901,13 @@ fn test_squash_from_multiple() {
901901
let output = work_dir.run_jj(["file", "show", "-r=d", "file"]);
902902
insta::assert_snapshot!(output, @r"
903903
<<<<<<< conflict 1 of 1
904-
%%%%%%% side #1 compared with base #1
904+
%%%%%%% squash destination (yqosqzyt 8acbb715) compared with parents of kkmpptxz fed4d1a2
905905
-a
906906
+d
907-
%%%%%%% side #2 compared with base #2
907+
%%%%%%% squashed commit (kkmpptxz fed4d1a2) compared with parents of mzvwutvl d7e94ec7
908908
-a
909909
+b
910-
+++++++ side #3
910+
+++++++ squashed commit (mzvwutvl d7e94ec7)
911911
c
912912
>>>>>>> conflict 1 of 1 ends
913913
[EOF]
@@ -1016,29 +1016,29 @@ fn test_squash_from_multiple_partial() {
10161016

10171017
// Partially squash a few commits sideways
10181018
let output = work_dir.run_jj(["squash", "--from=b|c", "--into=d", "file1"]);
1019-
insta::assert_snapshot!(output, @r###"
1019+
insta::assert_snapshot!(output, @r"
10201020
------- stderr -------
10211021
Rebased 2 descendant commits
1022-
Working copy (@) now at: kpqxywon a724910c f | (no description set)
1023-
Parent commit (@-) : yostqsxw 1bc405e1 e | (no description set)
1022+
Working copy (@) now at: kpqxywon 5f09dbd4 f | (no description set)
1023+
Parent commit (@-) : yostqsxw 602ec1ab e | (no description set)
10241024
New conflicts appeared in 1 commits:
1025-
yqosqzyt 7ddfe685 d | (conflict) (no description set)
1025+
yqosqzyt ed2751a9 d | (conflict) (no description set)
10261026
Hint: To resolve the conflicts, start by creating a commit on top of
10271027
the conflicted commit:
10281028
jj new yqosqzyt
10291029
Then use `jj resolve`, or edit the conflict markers in the file directly.
10301030
Once the conflicts are resolved, you can inspect the result with `jj diff`.
10311031
Then run `jj squash` to move the resolution into the conflicted commit.
10321032
[EOF]
1033-
"###);
1033+
");
10341034
insta::assert_snapshot!(get_log_output(&work_dir), @r"
1035-
@ a724910cd361 f
1036-
1bc405e12b68 e
1035+
@ 5f09dbd46785 f
1036+
602ec1ab8c78 e
10371037
├─┬─╮
10381038
│ │ ○ e9db15b956c4 b
10391039
│ ○ │ 83cbe51db94d c
10401040
│ ├─╯
1041-
× │ 7ddfe6857387 d
1041+
× │ ed2751a98f74 d
10421042
├─╯
10431043
○ 64ea60be8d77 a
10441044
◆ 000000000000 (empty)
@@ -1059,13 +1059,13 @@ fn test_squash_from_multiple_partial() {
10591059
let output = work_dir.run_jj(["file", "show", "-r=d", "file1"]);
10601060
insta::assert_snapshot!(output, @r"
10611061
<<<<<<< conflict 1 of 1
1062-
%%%%%%% side #1 compared with base #1
1062+
%%%%%%% squash destination (yqosqzyt f6812ff8) compared with parents of kkmpptxz f2c9709f
10631063
-a
10641064
+d
1065-
%%%%%%% side #2 compared with base #2
1065+
%%%%%%% squashed commit (selected changes from kkmpptxz f2c9709f) compared with parents of mzvwutvl aa908686
10661066
-a
10671067
+b
1068-
+++++++ side #3
1068+
+++++++ squashed commit (selected changes from mzvwutvl aa908686)
10691069
c
10701070
>>>>>>> conflict 1 of 1 ends
10711071
[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;
@@ -210,6 +211,15 @@ impl<T> Merge<T> {
210211
Self { values }
211212
}
212213

214+
/// Creates a `Merge` from a first side and a series of diffs to apply to
215+
/// that side.
216+
pub fn from_diffs(first_side: T, diffs: impl IntoIterator<Item = Diff<T>>) -> Self {
217+
let values = iter::once(first_side)
218+
.chain(diffs.into_iter().flat_map(|diff| [diff.before, diff.after]))
219+
.collect();
220+
Self { values }
221+
}
222+
213223
/// Creates a `Merge` with a single resolved value.
214224
pub const fn resolved(value: T) -> Self {
215225
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::IndexResult;
3939
use crate::iter_util::fallible_any;
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;
@@ -1155,6 +1156,20 @@ impl CommitWithSelection {
11551156
pub fn is_empty_selection(&self) -> bool {
11561157
self.selected_tree.tree_ids() == self.parent_tree.tree_ids()
11571158
}
1159+
1160+
/// Returns conflict labels for the diff represented by this selection.
1161+
pub fn conflict_labels(&self) -> BackendResult<Diff<String>> {
1162+
let commit_label = self.commit.conflict_label();
1163+
let parent_label = format!("parents of {commit_label}");
1164+
if self.is_full_selection() {
1165+
Ok(Diff::new(parent_label, commit_label))
1166+
} else {
1167+
Ok(Diff::new(
1168+
parent_label,
1169+
format!("selected changes from {commit_label}"),
1170+
))
1171+
}
1172+
}
11581173
}
11591174

11601175
/// Resulting commit builder and stats to be returned by [`squash_commits()`].
@@ -1177,6 +1192,7 @@ pub fn squash_commits<'repo>(
11771192
) -> BackendResult<Option<SquashedCommit<'repo>>> {
11781193
struct SourceCommit<'a> {
11791194
commit: &'a CommitWithSelection,
1195+
labels: Diff<String>,
11801196
abandon: bool,
11811197
}
11821198
let mut source_commits = vec![];
@@ -1193,6 +1209,7 @@ pub fn squash_commits<'repo>(
11931209
// squash -r`)? The source tree will be unchanged in that case.
11941210
source_commits.push(SourceCommit {
11951211
commit: source,
1212+
labels: source.conflict_labels()?,
11961213
abandon,
11971214
});
11981215
}
@@ -1209,12 +1226,18 @@ pub fn squash_commits<'repo>(
12091226
} else {
12101227
let source_tree = source.commit.commit.tree();
12111228
// Apply the reverse of the selected changes onto the source
1212-
let new_source_tree = source_tree
1213-
.merge_unlabeled(
1229+
let new_source_tree = MergedTree::merge(Merge::from_vec(vec![
1230+
(source_tree, source.commit.commit.conflict_label()),
1231+
(
12141232
source.commit.selected_tree.clone(),
1233+
source.labels.after.clone(),
1234+
),
1235+
(
12151236
source.commit.parent_tree.clone(),
1216-
)
1217-
.block_on()?;
1237+
source.labels.before.clone(),
1238+
),
1239+
]))
1240+
.block_on()?;
12181241
repo.rewrite_commit(&source.commit.commit)
12191242
.set_tree(new_source_tree)
12201243
.write()?;
@@ -1244,22 +1267,29 @@ pub fn squash_commits<'repo>(
12441267
};
12451268
})?;
12461269
}
1247-
// Apply the selected changes onto the destination
1248-
let mut destination_tree = rewritten_destination.tree();
1249-
for source in &source_commits {
1250-
destination_tree = destination_tree
1251-
.merge_unlabeled(
1252-
source.commit.parent_tree.clone(),
1253-
source.commit.selected_tree.clone(),
1254-
)
1255-
.block_on()?;
1256-
}
12571270
let mut predecessors = vec![destination.id().clone()];
12581271
predecessors.extend(
12591272
source_commits
12601273
.iter()
12611274
.map(|source| source.commit.commit.id().clone()),
12621275
);
1276+
// Apply the selected changes onto the destination
1277+
let destination_tree = MergedTree::merge(Merge::from_diffs(
1278+
(
1279+
rewritten_destination.tree(),
1280+
format!("squash destination ({})", destination.conflict_label()),
1281+
),
1282+
source_commits.into_iter().map(|source| {
1283+
Diff::new(
1284+
(source.commit.parent_tree.clone(), source.labels.before),
1285+
(
1286+
source.commit.selected_tree.clone(),
1287+
format!("squashed commit ({})", source.labels.after),
1288+
),
1289+
)
1290+
}),
1291+
))
1292+
.block_on()?;
12631293

12641294
let commit_builder = repo
12651295
.rewrite_commit(&rewritten_destination)

0 commit comments

Comments
 (0)