Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
conflict_labels: add functions with defaults for missing labels
This removes the special case for using "base" instead of "base #1" when
there's only one base, but since all new conflicts will have labels,
this should be fine.
  • Loading branch information
scott2000 committed Mar 11, 2026
commit 757c57ffdbdf3ce80274df57455ebf4313b40bd1
4 changes: 2 additions & 2 deletions cli/tests/test_evolog_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ fn test_evolog_squash() {
│ │ 1 : <<<<<<< conflict 1 of 1
│ │ 2 : +++++++ side #1
│ │ 3 1: squashed 1
│ │ 4 : %%%%%%% diff from: base
│ │ 4 : %%%%%%% diff from: base #1
│ │ 5 1: \\\\\\\ to: side #2
│ │ 6 : +third
│ │ 7 : >>>>>>> conflict 1 of 1 ends
Expand Down Expand Up @@ -489,7 +489,7 @@ fn test_evolog_squash() {
│ │ -- operation 65c81703100d squash commits into 5878cbe03cdf599c9353e5a1a52a01f4c5e0e0fa
│ │ Modified commit description:
│ │ 1 : <<<<<<< conflict 1 of 1
│ │ 2 : %%%%%%% diff from: base
│ │ 2 : %%%%%%% diff from: base #1
│ │ 3 : \\\\\\\ to: side #1
│ │ 4 : +first
│ │ 5 : +++++++ side #2
Expand Down
22 changes: 22 additions & 0 deletions lib/src/conflict_labels.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

//! Labels for conflicted trees.

use std::borrow::Cow;
use std::fmt;

use crate::merge::Merge;
Expand Down Expand Up @@ -94,6 +95,12 @@ impl ConflictLabels {
.map(String::as_str)
}

/// Get the label for a side at an index or a default label.
pub fn get_add_or_default(&self, add_index: usize) -> Cow<'_, str> {
self.get_add(add_index)
.map_or_else(|| format!("side #{}", add_index + 1).into(), Cow::from)
}

/// Get the label for a base at an index.
pub fn get_remove(&self, remove_index: usize) -> Option<&str> {
self.labels
Expand All @@ -102,6 +109,21 @@ impl ConflictLabels {
.map(String::as_str)
}

/// Get the label for a base at an index or a default label.
pub fn get_remove_or_default(&self, remove_index: usize) -> Cow<'_, str> {
self.get_remove(remove_index)
.map_or_else(|| format!("base #{}", remove_index + 1).into(), Cow::from)
}

/// Get the label at a given index (alternating adds and removes).
pub fn get_or_default(&self, index: usize) -> Cow<'_, str> {
if index.is_multiple_of(2) {
self.get_add_or_default(index / 2)
} else {
self.get_remove_or_default(index / 2)
}
}

/// 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<T: PartialEq + Clone>(&self, merge: &Merge<T>) -> (Self, Merge<T>) {
Expand Down
35 changes: 10 additions & 25 deletions lib/src/conflicts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ use crate::files;
use crate::files::MergeResult;
use crate::merge::Diff;
use crate::merge::Merge;
use crate::merge::MergeBuilder;
use crate::merge::MergedTreeValue;
use crate::merge::SameChange;
use crate::repo_path::RepoPath;
Expand Down Expand Up @@ -574,31 +575,15 @@ struct HunkTerm {
}

fn build_hunk_sides(hunk: Merge<BString>, labels: &ConflictLabels) -> Merge<HunkTerm> {
let (removes, adds) = hunk.into_removes_adds();
let num_bases = removes.len();
let removes = removes.enumerate().map(|(base_index, contents)| {
let label = 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 num_bases == 1 {
"base".to_string()
} else {
format!("base #{}", base_index + 1)
}
});
HunkTerm { contents, label }
});
let adds = adds.enumerate().map(|(add_index, contents)| {
let label = labels.get_add(add_index).map_or_else(
|| format!("side #{}", add_index + 1),
|label| label.to_owned(),
);
HunkTerm { contents, label }
});
let mut hunk_terms = Merge::from_removes_adds(removes, adds);
let mut hunk_terms = hunk
.into_iter()
.enumerate()
.map(|(index, contents)| HunkTerm {
contents,
label: labels.get_or_default(index).into_owned(),
})
.collect::<MergeBuilder<_>>()
.build();
for term in &mut hunk_terms {
// We don't add the no eol comment if the side is empty.
if term.contents.last().is_some_and(|ch| *ch != b'\n') {
Expand Down
48 changes: 24 additions & 24 deletions lib/tests/test_conflicts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ fn test_materialize_conflict_basic() {
left 3.1
left 3.2
left 3.3
%%%%%%% diff from: base
%%%%%%% diff from: base #1
\\\\\\\ to: side #2
-line 3
+right 3.1
Expand All @@ -116,7 +116,7 @@ fn test_materialize_conflict_basic() {
line 1
line 2
<<<<<<< conflict 1 of 1
%%%%%%% diff from: base
%%%%%%% diff from: base #1
\\\\\\\ to: side #1
-line 3
+right 3.1
Expand Down Expand Up @@ -144,7 +144,7 @@ fn test_materialize_conflict_basic() {
left 3.1
left 3.2
left 3.3
------- base
------- base #1
line 3
+++++++ side #2
right 3.1
Expand All @@ -167,7 +167,7 @@ fn test_materialize_conflict_basic() {
left 3.1
left 3.2
left 3.3
||||||| base
||||||| base #1
line 3
=======
right 3.1
Expand Down Expand Up @@ -531,15 +531,15 @@ fn test_materialize_parse_roundtrip() {
+++++++ side #1
line 1 left
line 2 left
%%%%%%% diff from: base
%%%%%%% diff from: base #1
\\\\\\\ to: side #2
-line 1
+line 1 right
line 2
>>>>>>> conflict 1 of 2 ends
line 3
<<<<<<< conflict 2 of 2
%%%%%%% diff from: base
%%%%%%% diff from: base #1
\\\\\\\ to: side #1
line 4
-line 5
Expand Down Expand Up @@ -659,7 +659,7 @@ fn test_materialize_conflict_no_newlines_at_eof() {
insta::assert_snapshot!(materialized.to_owned() + "[EOF]",
@r"
<<<<<<< conflict 1 of 1
%%%%%%% diff from: base (no terminating newline)
%%%%%%% diff from: base #1 (no terminating newline)
\\\\\\\ to: side #1
-base
+
Expand Down Expand Up @@ -741,7 +741,7 @@ fn test_materialize_conflict_modify_delete() {
<<<<<<< conflict 1 of 1
+++++++ side #1
modified
%%%%%%% diff from: base
%%%%%%% diff from: base #1
\\\\\\\ to: side #2
-line 3
>>>>>>> conflict 1 of 1 ends
Expand All @@ -759,7 +759,7 @@ fn test_materialize_conflict_modify_delete() {
line 1
line 2
<<<<<<< conflict 1 of 1
%%%%%%% diff from: base
%%%%%%% diff from: base #1
\\\\\\\ to: side #1
-line 3
+++++++ side #2
Expand All @@ -777,7 +777,7 @@ fn test_materialize_conflict_modify_delete() {
);
insta::assert_snapshot!(&materialize_conflict_string(store, path, &conflict, ConflictMarkerStyle::Diff), @r"
<<<<<<< conflict 1 of 1
%%%%%%% diff from: base
%%%%%%% diff from: base #1
\\\\\\\ to: side #1
line 1
line 2
Expand Down Expand Up @@ -1880,7 +1880,7 @@ fn test_update_conflict_from_content_simplified_conflict() {
materialized,
@r"
<<<<<<< conflict 1 of 2
%%%%%%% diff from: base
%%%%%%% diff from: base #1
\\\\\\\ to: side #1
-line 1
+left 1
Expand All @@ -1889,7 +1889,7 @@ fn test_update_conflict_from_content_simplified_conflict() {
>>>>>>> conflict 1 of 2 ends
line 2
<<<<<<< conflict 2 of 2
%%%%%%% diff from: base
%%%%%%% diff from: base #1
\\\\\\\ to: side #1
-line 3
+left 3
Expand Down Expand Up @@ -1992,7 +1992,7 @@ fn test_update_conflict_from_content_with_long_markers() {
<<<<<<<<<<<<<<<< conflict 1 of 2
++++++++++++++++ side #1
<<<< left 1
---------------- base
---------------- base #1
line 1
++++++++++++++++ side #2
>>>>>>> right 1
Expand All @@ -2001,7 +2001,7 @@ fn test_update_conflict_from_content_with_long_markers() {
<<<<<<<<<<<<<<<< conflict 2 of 2
++++++++++++++++ side #1
<<<<<<<<<<<< left 3
---------------- base
---------------- base #1
line 3
++++++++++++++++ side #2
>>>>>>>>>>>> right 3
Expand Down Expand Up @@ -2094,7 +2094,7 @@ fn test_update_conflict_from_content_with_long_markers() {
<<<<<<<<<<< conflict 1 of 1
+++++++++++ side #1
<<<< left 1
----------- base
----------- base #1
line 1
+++++++++++ side #2
>>>>>>> right 1
Expand Down Expand Up @@ -2127,7 +2127,7 @@ fn test_update_conflict_from_content_no_eol() {
@r"
line 1
<<<<<<< conflict 1 of 2
%%%%%%% diff from: base
%%%%%%% diff from: base #1
\\\\\\\ to: side #1
-line 2
+line 2 left
Expand All @@ -2136,7 +2136,7 @@ fn test_update_conflict_from_content_no_eol() {
>>>>>>> conflict 1 of 2 ends
line 3
<<<<<<< conflict 2 of 2
%%%%%%% diff from: base (no terminating newline)
%%%%%%% diff from: base #1 (no terminating newline)
\\\\\\\ to: side #1
base
+left
Expand Down Expand Up @@ -2167,7 +2167,7 @@ fn test_update_conflict_from_content_no_eol() {
<<<<<<< conflict 1 of 2
+++++++ side #1
line 2 left
------- base
------- base #1
line 2
+++++++ side #2
line 2 right
Expand All @@ -2178,7 +2178,7 @@ fn test_update_conflict_from_content_no_eol() {
base
left

------- base (no terminating newline)
------- base #1 (no terminating newline)
base
+++++++ side #2 (no terminating newline)
right
Expand All @@ -2205,7 +2205,7 @@ fn test_update_conflict_from_content_no_eol() {
line 1
<<<<<<< side #1
line 2 left
||||||| base
||||||| base #1
line 2
=======
line 2 right
Expand All @@ -2215,7 +2215,7 @@ fn test_update_conflict_from_content_no_eol() {
base
left

||||||| base (no terminating newline)
||||||| base #1 (no terminating newline)
base
=======
right
Expand Down Expand Up @@ -2338,7 +2338,7 @@ fn test_update_conflict_from_content_only_no_eol_change() {
<<<<<<< conflict 1 of 1
+++++++ side #1 (no terminating newline)
line 2
%%%%%%% diff from: base
%%%%%%% diff from: base #1
\\\\\\\ to: side #2
+line 2

Expand Down Expand Up @@ -2416,7 +2416,7 @@ fn test_update_from_content_malformed_conflict() {
insta::assert_snapshot!(materialized, @r"
line 1
<<<<<<< conflict 1 of 2
%%%%%%% diff from: base
%%%%%%% diff from: base #1
\\\\\\\ to: side #1
-line 2
+line 2 left
Expand All @@ -2425,7 +2425,7 @@ fn test_update_from_content_malformed_conflict() {
>>>>>>> conflict 1 of 2 ends
line 3
<<<<<<< conflict 2 of 2
%%%%%%% diff from: base
%%%%%%% diff from: base #1
\\\\\\\ to: side #1
-line 4
+line 4 left
Expand Down