Skip to content

Commit e507f08

Browse files
committed
implement support for resolving irreconcilable tree conflicts with 'ours' or 'ancestor'
1 parent 3228de6 commit e507f08

File tree

5 files changed

+128
-5
lines changed

5 files changed

+128
-5
lines changed

gix-merge/src/tree/function.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,12 @@ where
129129

130130
let mut conflicts = Vec::new();
131131
let mut failed_on_first_conflict = false;
132-
let mut should_fail_on_conflict = |conflict: Conflict| -> bool {
132+
let mut should_fail_on_conflict = |mut conflict: Conflict| -> bool {
133+
if options.tree_conflicts.is_some() {
134+
if let Err(failure) = conflict.resolution {
135+
conflict.resolution = Ok(Resolution::Forced(failure));
136+
}
137+
}
133138
if let Some(how) = options.fail_on_conflict {
134139
if conflict.resolution.is_err() || conflict.is_unresolved(how) {
135140
failed_on_first_conflict = true;
@@ -284,6 +289,7 @@ where
284289
|| ours.source_location() == theirs.source_location()),
285290
"BUG: right now it's not known to be possible to match changes from different paths: {match_kind:?} {candidate:?}"
286291
);
292+
dbg!(&ours, &theirs);
287293
match (ours, theirs) {
288294
(
289295
Change::Modification {
@@ -623,7 +629,9 @@ where
623629
};
624630
change_on_right
625631
.map(|change| {
626-
change.inner.entry_mode().is_tree() && change.inner.location() == location
632+
change.inner.entry_mode().is_tree()
633+
&& change.inner.location() == location
634+
&& matches!(change.inner, Change::Addition { .. })
627635
})
628636
.unwrap_or_default()
629637
};
8.5 KB
Binary file not shown.

gix-merge/tests/fixtures/tree-baseline.sh

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,15 @@ function make_conflict_index() {
3636
cp .git/index .git/"${identifier}".index
3737
}
3838

39+
function make_resolve_tree() {
40+
local resolve=${1:?Their 'ancestor' or 'ours'}
41+
local our_side=${2:-}
42+
local their_side=${3:-}
43+
44+
local filename="resolve-${our_side}-${their_side}-with-${resolve}"
45+
git write-tree > ".git/${filename}.tree"
46+
}
47+
3948
function baseline () (
4049
local dir=${1:?the directory to enter}
4150
local output_name=${2:?the basename of the output of the merge}
@@ -1074,3 +1083,43 @@ baseline rename-and-modification A-B A B
10741083
baseline symlink-modification A-B A B
10751084
baseline symlink-addition A-B A B
10761085
baseline type-change-to-symlink A-B A B
1086+
1087+
##
1088+
## Only once the tree-merges were performed can we refer to their objects
1089+
## when making tree-conflict resolution expectations. It's important
1090+
## to get these right.
1091+
##
1092+
(cd simple
1093+
rm .git/index
1094+
# 'whatever' is tree-conflict, 'greeting' is content conflict with markers
1095+
git update-index --index-info <<EOF
1096+
100644 9dc97bdc2426e68423360e3e5299280b2cf6b8ff 0 greeting
1097+
100644 09c277aa66897c58157f57a374eacc63a407dcab 0 numbers
1098+
100644 5716ca5987cbf97d6bb54920bea6adde242d87e6 0 whatever
1099+
EOF
1100+
make_resolve_tree ours side1 side2
1101+
1102+
rm .git/index
1103+
git update-index --index-info <<EOF
1104+
100644 7ee0500b60d7a0682a8eee44369bb3df116054c7 0 greeting
1105+
100644 09c277aa66897c58157f57a374eacc63a407dcab 0 numbers
1106+
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 whatever/empty
1107+
EOF
1108+
make_resolve_tree ours side2 side1
1109+
1110+
rm .git/index
1111+
git update-index --index-info <<EOF
1112+
100644 9dc97bdc2426e68423360e3e5299280b2cf6b8ff 0 greeting
1113+
100644 09c277aa66897c58157f57a374eacc63a407dcab 0 numbers
1114+
100644 257cc5642cb1a054f08cc83f2d943e56fd3ebe99 0 whatever
1115+
EOF
1116+
make_resolve_tree ancestor side1 side2
1117+
1118+
rm .git/index
1119+
git update-index --index-info <<EOF
1120+
100644 7ee0500b60d7a0682a8eee44369bb3df116054c7 0 greeting
1121+
100644 09c277aa66897c58157f57a374eacc63a407dcab 0 numbers
1122+
100644 257cc5642cb1a054f08cc83f2d943e56fd3ebe99 0 whatever
1123+
EOF
1124+
make_resolve_tree ancestor side1 side2
1125+
)

gix-merge/tests/merge/tree/baseline.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,22 @@ pub fn show_diff_and_fail(
371371
);
372372
}
373373

374+
pub fn show_diff_trees_and_fail(
375+
case_name: &str,
376+
actual_id: ObjectId,
377+
actual: &gix_merge::tree::Outcome<'_>,
378+
expected_tree_id: gix_hash::ObjectId,
379+
additional_information: &str,
380+
odb: &gix_odb::memory::Proxy<gix_odb::Handle>,
381+
) {
382+
pretty_assertions::assert_str_eq!(
383+
visualize_tree(&actual_id, odb, None).to_string(),
384+
visualize_tree(&expected_tree_id, odb, None).to_string(),
385+
"{case_name}: merged tree mismatch\n{:#?}\n{additional_information}\n{case_name}",
386+
actual.conflicts,
387+
);
388+
}
389+
374390
pub(crate) fn apply_git_index_entries(conflicts: &[Conflict], state: &mut gix_index::State) {
375391
let len = state.entries().len();
376392
for Conflict { ours, theirs, ancestor } in conflicts {

gix-merge/tests/merge/tree/mod.rs

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ fn run_baseline() -> crate::Result {
2121
let root = gix_testtools::scripted_fixture_read_only("tree-baseline.sh")?;
2222
let cases = std::fs::read_to_string(root.join("baseline.cases"))?;
2323
let mut actual_cases = 0;
24-
// let new_test = Some("rename-add-symlink-A-B");
25-
let new_test = None;
24+
let mut skipped_tree_resolve_cases = 0;
25+
let new_test = Some("simple-side-1-2-various-conflicts");
26+
// let new_test = None;
2627
for baseline::Expectation {
2728
root,
2829
conflict_style,
@@ -63,7 +64,7 @@ fn run_baseline() -> crate::Result {
6364
&mut blob_merge,
6465
&odb,
6566
&mut |id| id.to_hex_with_len(7).to_string(),
66-
options,
67+
options.clone(),
6768
)?
6869
.tree_merge;
6970

@@ -138,8 +139,57 @@ fn run_baseline() -> crate::Result {
138139
actual.has_unresolved_conflicts(conflicts_like_in_git),
139140
"{case_name}: If there is any kind of conflict, the index should have been changed"
140141
);
142+
143+
// The content-merge mode is not relevant for the upcoming tree-conflict resolution.
144+
if case_name.contains("diff3") {
145+
continue;
146+
}
147+
148+
for tree_resolution in [
149+
gix_merge::tree::ResolveWith::Ancestor,
150+
gix_merge::tree::ResolveWith::Ours,
151+
] {
152+
let resolution_name = match tree_resolution {
153+
gix_merge::tree::ResolveWith::Ancestor => "ancestor",
154+
gix_merge::tree::ResolveWith::Ours => "ours",
155+
};
156+
let basename = format!("resolve-{our_side_name}-{their_side_name}-with-{resolution_name}");
157+
let tree_path = root.join(".git").join(format!("{basename}.tree"));
158+
if !tree_path.exists() {
159+
skipped_tree_resolve_cases += 1;
160+
continue;
161+
};
162+
let expected_tree_id = gix_hash::ObjectId::from_hex(std::fs::read_to_string(tree_path)?.trim().as_bytes())?;
163+
options.tree_merge.tree_conflicts = Some(tree_resolution);
164+
let actual = gix_merge::commit(
165+
our_commit_id,
166+
their_commit_id,
167+
gix_merge::blob::builtin_driver::text::Labels {
168+
ancestor: None,
169+
current: Some(our_side_name.as_str().into()),
170+
other: Some(their_side_name.as_str().into()),
171+
},
172+
&mut graph,
173+
&mut diff_resource_cache,
174+
&mut blob_merge,
175+
&odb,
176+
&mut |id| id.to_hex_with_len(7).to_string(),
177+
options.clone(),
178+
)?
179+
.tree_merge;
180+
181+
if actual_id != expected_tree_id {
182+
baseline::show_diff_trees_and_fail(&case_name, actual_id, &actual, expected_tree_id, &basename, &odb);
183+
}
184+
}
141185
}
142186

187+
assert_eq!(
188+
skipped_tree_resolve_cases,
189+
166 - 4 * 7,
190+
"this is done when no case is skipped, and we don't want to accidentally skip them.\
191+
Some don't actually have conflicts"
192+
);
143193
assert_eq!(
144194
actual_cases, 109,
145195
"BUG: update this number, and don't forget to remove a filter in the end"

0 commit comments

Comments
 (0)