gix_merge/tree/function.rs
1use crate::tree::utils::{
2 apply_change, perform_blob_merge, possibly_rewritten_location, rewrite_location_with_renamed_directory,
3 to_components, track, unique_path_in_tree, ChangeList, ChangeListRef, PossibleConflict, TrackedChange, TreeNodes,
4};
5use crate::tree::ConflictMapping::{Original, Swapped};
6use crate::tree::{
7 Conflict, ConflictIndexEntry, ConflictIndexEntryPathHint, ConflictMapping, ContentMerge, Error, Options, Outcome,
8 Resolution, ResolutionFailure, ResolveWith,
9};
10use bstr::{BString, ByteSlice};
11use gix_diff::tree::recorder::Location;
12use gix_diff::tree_with_rewrites::Change;
13use gix_hash::ObjectId;
14use gix_object::tree::{EntryKind, EntryMode};
15use gix_object::{tree, FindExt};
16use std::borrow::Cow;
17use std::convert::Infallible;
18
19/// Perform a merge between `our_tree` and `their_tree`, using `base_tree` as merge-base.
20/// Note that `base_tree` can be an empty tree to indicate 'no common ancestor between the two sides'.
21///
22/// * `labels` are relevant for text-merges and will be shown in conflicts.
23/// * `objects` provides access to trees when diffing them.
24/// * `write_blob_to_odb(content) -> Result<ObjectId, E>` writes newly merged content into the odb to obtain an id
25/// that will be used in merged trees.
26/// * `diff_state` is state used for diffing trees.
27/// * `diff_resource_cache` is used for similarity checks.
28/// * `blob_merge` is a pre-configured platform to merge any content.
29/// - Note that it shouldn't be allowed to read from the worktree, given that this is a tree-merge.
30/// * `options` are used to affect how the merge is performed.
31///
32/// ### Unbiased (Ours x Theirs == Theirs x Ours)
33///
34/// The algorithm is implemented so that the result is the same no matter how the sides are ordered.
35///
36/// ### Differences to Merge-ORT
37///
38/// Merge-ORT (Git) defines the desired outcomes where are merely mimicked here. The algorithms are different, and it's
39/// clear that Merge-ORT is significantly more elaborate and general.
40///
41/// It also writes out trees once it's done with them in a form of reduction process, here an editor is used
42/// to keep only the changes, to be written by the caller who receives it as part of the result.
43/// This may use more memory in the worst case scenario, but in average *shouldn't* perform much worse due to the
44/// natural sparsity of the editor.
45///
46/// Our rename-tracking also produces copy information, but we discard it and simply treat it like an addition.
47///
48/// Finally, our algorithm will consider reasonable solutions to merge-conflicts as conflicts that are resolved, leaving
49/// only content with conflict markers as unresolved ones.
50///
51/// ### Performance
52///
53/// Note that `objects` *should* have an object cache to greatly accelerate tree-retrieval.
54#[allow(clippy::too_many_arguments)]
55pub fn tree<'objects, E>(
56 base_tree: &gix_hash::oid,
57 our_tree: &gix_hash::oid,
58 their_tree: &gix_hash::oid,
59 mut labels: crate::blob::builtin_driver::text::Labels<'_>,
60 objects: &'objects impl gix_object::FindObjectOrHeader,
61 mut write_blob_to_odb: impl FnMut(&[u8]) -> Result<ObjectId, E>,
62 diff_state: &mut gix_diff::tree::State,
63 diff_resource_cache: &mut gix_diff::blob::Platform,
64 blob_merge: &mut crate::blob::Platform,
65 options: Options,
66) -> Result<Outcome<'objects>, Error>
67where
68 E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
69{
70 let ours_needs_diff = base_tree != our_tree;
71 let theirs_needs_diff = base_tree != their_tree;
72 let _span = gix_trace::coarse!("gix_merge::tree", ?base_tree, ?our_tree, ?their_tree, ?labels);
73 let (mut base_buf, mut side_buf) = (Vec::new(), Vec::new());
74 let ancestor_tree = objects.find_tree(base_tree, &mut base_buf)?;
75 let mut editor = tree::Editor::new(ancestor_tree.to_owned(), objects, base_tree.kind());
76 let ancestor_tree = gix_object::TreeRefIter::from_bytes(&base_buf);
77 let tree_conflicts = options.tree_conflicts;
78
79 let mut our_changes = Vec::new();
80 if ours_needs_diff {
81 let our_tree = objects.find_tree_iter(our_tree, &mut side_buf)?;
82 gix_diff::tree_with_rewrites(
83 ancestor_tree,
84 our_tree,
85 diff_resource_cache,
86 diff_state,
87 objects,
88 |change| -> Result<_, Infallible> {
89 track(change, &mut our_changes);
90 Ok(gix_diff::tree_with_rewrites::Action::Continue)
91 },
92 gix_diff::tree_with_rewrites::Options {
93 location: Some(Location::Path),
94 rewrites: options.rewrites,
95 },
96 )?;
97 }
98
99 let mut our_tree = TreeNodes::new();
100 for (idx, change) in our_changes.iter().enumerate() {
101 our_tree.track_change(&change.inner, idx);
102 }
103
104 let mut their_changes = Vec::new();
105 if theirs_needs_diff {
106 let their_tree = objects.find_tree_iter(their_tree, &mut side_buf)?;
107 gix_diff::tree_with_rewrites(
108 ancestor_tree,
109 their_tree,
110 diff_resource_cache,
111 diff_state,
112 objects,
113 |change| -> Result<_, Infallible> {
114 track(change, &mut their_changes);
115 Ok(gix_diff::tree_with_rewrites::Action::Continue)
116 },
117 gix_diff::tree_with_rewrites::Options {
118 location: Some(Location::Path),
119 rewrites: options.rewrites,
120 },
121 )?;
122 }
123
124 let mut their_tree = TreeNodes::new();
125 for (idx, change) in their_changes.iter().enumerate() {
126 their_tree.track_change(&change.inner, idx);
127 }
128
129 let mut conflicts = Vec::new();
130 let mut failed_on_first_conflict = false;
131 let mut should_fail_on_conflict = |mut conflict: Conflict| -> bool {
132 if tree_conflicts.is_some() {
133 if let Err(failure) = conflict.resolution {
134 conflict.resolution = Ok(Resolution::Forced(failure));
135 }
136 }
137 if let Some(how) = options.fail_on_conflict {
138 if conflict.resolution.is_err() || conflict.is_unresolved(how) {
139 failed_on_first_conflict = true;
140 }
141 }
142 conflicts.push(conflict);
143 failed_on_first_conflict
144 };
145
146 let ((mut our_changes, mut our_tree), (mut their_changes, mut their_tree)) =
147 ((&mut our_changes, &mut our_tree), (&mut their_changes, &mut their_tree));
148 let mut outer_side = Original;
149 if their_changes.is_empty() {
150 ((our_changes, our_tree), (their_changes, their_tree)) = ((their_changes, their_tree), (our_changes, our_tree));
151 (labels.current, labels.other) = (labels.other, labels.current);
152 outer_side = outer_side.swapped();
153 }
154
155 #[derive(Debug)]
156 enum MatchKind {
157 /// A tree is supposed to be superseded by something else.
158 EraseTree,
159 /// A leaf node is superseded by a tree
160 EraseLeaf,
161 }
162
163 'outer: while their_changes.iter().rev().any(|c| !c.was_written) {
164 let mut segment_start = 0;
165 let mut last_seen_len = their_changes.len();
166
167 while segment_start != last_seen_len {
168 for theirs_idx in segment_start..last_seen_len {
169 // `their` can be a tree, and it could be used to efficiently prune child-changes as these
170 // trees are always rewrites with parent ids (of course we validate), so child-changes could be handled
171 // quickly. However, for now the benefit of having these trees is to have them as part of the match-tree
172 // on *our* side so that it's clear that we passed a renamed directory (by identity).
173 let TrackedChange {
174 inner: theirs,
175 was_written,
176 needs_tree_insertion,
177 rewritten_location,
178 } = &their_changes[theirs_idx];
179 if theirs.entry_mode().is_tree() || *was_written {
180 continue;
181 }
182
183 if needs_tree_insertion.is_some() {
184 their_tree.insert(theirs, theirs_idx);
185 }
186
187 match our_tree
188 .check_conflict(
189 rewritten_location
190 .as_ref()
191 .map_or_else(|| theirs.source_location(), |t| t.0.as_bstr()),
192 )
193 .filter(|ours| {
194 ours.change_idx()
195 .zip(needs_tree_insertion.flatten())
196 .map_or(true, |(ours_idx, ignore_idx)| ours_idx != ignore_idx)
197 && our_tree.is_not_same_change_in_possible_conflict(theirs, ours, our_changes)
198 }) {
199 None => {
200 if let Some((rewritten_location, ours_idx)) = rewritten_location {
201 // `no_entry` to the index because that's not a conflict at all,
202 // but somewhat advanced rename tracking.
203 if should_fail_on_conflict(Conflict::with_resolution(
204 Resolution::SourceLocationAffectedByRename {
205 final_location: rewritten_location.to_owned(),
206 },
207 (&our_changes[*ours_idx].inner, theirs, Original, outer_side),
208 [None, None, None],
209 )) {
210 break 'outer;
211 }
212 editor.remove(to_components(theirs.location()))?;
213 }
214 apply_change(&mut editor, theirs, rewritten_location.as_ref().map(|t| &t.0))?;
215 their_changes[theirs_idx].was_written = true;
216 }
217 Some(candidate) => {
218 use crate::tree::utils::to_components_bstring_ref as toc;
219 debug_assert!(
220 rewritten_location.is_none(),
221 "We should probably handle the case where a rewritten location is passed down here"
222 );
223
224 let (ours_idx, match_kind) = match candidate {
225 PossibleConflict::PassedRewrittenDirectory { change_idx } => {
226 let ours = &our_changes[change_idx];
227 let location_after_passed_rename =
228 rewrite_location_with_renamed_directory(theirs.location(), &ours.inner);
229 if let Some(new_location) = location_after_passed_rename {
230 their_tree.remove_existing_leaf(theirs.location());
231 push_deferred_with_rewrite(
232 (theirs.clone(), Some(change_idx)),
233 Some((new_location, change_idx)),
234 their_changes,
235 );
236 } else {
237 apply_change(&mut editor, theirs, None)?;
238 their_changes[theirs_idx].was_written = true;
239 }
240 their_changes[theirs_idx].was_written = true;
241 continue;
242 }
243 PossibleConflict::TreeToNonTree { change_idx: Some(idx) }
244 if matches!(
245 our_changes[idx].inner,
246 Change::Deletion { .. } | Change::Addition { .. } | Change::Rewrite { .. }
247 ) =>
248 {
249 (Some(idx), Some(MatchKind::EraseTree))
250 }
251 PossibleConflict::NonTreeToTree { change_idx } => (change_idx, Some(MatchKind::EraseLeaf)),
252 PossibleConflict::Match { change_idx: ours_idx } => (Some(ours_idx), None),
253 _ => (None, None),
254 };
255
256 let Some(ours_idx) = ours_idx else {
257 let ours = match candidate {
258 PossibleConflict::TreeToNonTree { change_idx, .. }
259 | PossibleConflict::NonTreeToTree { change_idx, .. } => change_idx,
260 PossibleConflict::Match { change_idx }
261 | PossibleConflict::PassedRewrittenDirectory { change_idx } => Some(change_idx),
262 }
263 .map(|idx| &mut our_changes[idx]);
264
265 if let Some(ours) = ours {
266 gix_trace::debug!("Turning a case we could probably handle into a conflict for now. theirs: {theirs:#?} ours: {ours:#?} kind: {match_kind:?}");
267 let conflict = Conflict::unknown((&ours.inner, theirs, Original, outer_side));
268 if let Some(ResolveWith::Ours) = tree_conflicts {
269 apply_our_resolution(&ours.inner, theirs, outer_side, &mut editor)?;
270 *match outer_side {
271 Original => &mut ours.was_written,
272 Swapped => &mut their_changes[theirs_idx].was_written,
273 } = true;
274 }
275 if should_fail_on_conflict(conflict) {
276 break 'outer;
277 }
278 } else if matches!(candidate, PossibleConflict::TreeToNonTree { .. }) {
279 let (mode, id) = theirs.entry_mode_and_id();
280 let location = theirs.location();
281 let renamed_location = unique_path_in_tree(
282 location.as_bstr(),
283 &editor,
284 their_tree,
285 labels.other.unwrap_or_default(),
286 )?;
287 match tree_conflicts {
288 None => {
289 editor.upsert(toc(&renamed_location), mode.kind(), id.to_owned())?;
290 }
291 Some(ResolveWith::Ours) => {
292 if outer_side.is_swapped() {
293 editor.upsert(to_components(location), mode.kind(), id.to_owned())?;
294 }
295 }
296 Some(ResolveWith::Ancestor) => {
297 // we found no matching node of 'ours', so nothing to apply here.
298 }
299 }
300
301 let conflict = Conflict::without_resolution(
302 ResolutionFailure::OursDirectoryTheirsNonDirectoryTheirsRenamed {
303 renamed_unique_path_of_theirs: renamed_location,
304 },
305 (theirs, theirs, Original, outer_side),
306 [
307 None,
308 None,
309 index_entry_at_path(
310 &mode.kind().into(),
311 &id.to_owned(),
312 ConflictIndexEntryPathHint::RenamedOrTheirs,
313 ),
314 ],
315 );
316 their_changes[theirs_idx].was_written = true;
317 if should_fail_on_conflict(conflict) {
318 break 'outer;
319 }
320 } else if matches!(candidate, PossibleConflict::NonTreeToTree { .. }) {
321 // We are writing on top of what was a file, a conflict we probably already saw and dealt with.
322 let location = theirs.location();
323 let (mode, id) = theirs.entry_mode_and_id();
324 editor.upsert(to_components(location), mode.kind(), id.to_owned())?;
325 their_changes[theirs_idx].was_written = true;
326 } else {
327 gix_trace::debug!("Couldn't figure out how to handle {match_kind:?} theirs: {theirs:#?} candidate: {candidate:#?}");
328 }
329 continue;
330 };
331
332 let ours = &our_changes[ours_idx].inner;
333 match (ours, theirs) {
334 (
335 Change::Modification {
336 previous_id,
337 previous_entry_mode,
338 id: our_id,
339 location: our_location,
340 entry_mode: our_mode,
341 ..
342 },
343 Change::Rewrite {
344 source_id: their_source_id,
345 id: their_id,
346 location: their_location,
347 entry_mode: their_mode,
348 source_location,
349 ..
350 },
351 )
352 | (
353 Change::Rewrite {
354 source_id: their_source_id,
355 id: their_id,
356 location: their_location,
357 entry_mode: their_mode,
358 source_location,
359 ..
360 },
361 Change::Modification {
362 previous_id,
363 previous_entry_mode,
364 id: our_id,
365 location: our_location,
366 entry_mode: our_mode,
367 ..
368 },
369 ) => {
370 let side = if matches!(ours, Change::Modification { .. }) {
371 Original
372 } else {
373 Swapped
374 };
375 if let Some(merged_mode) = merge_modes(*our_mode, *their_mode) {
376 debug_assert_eq!(
377 previous_id, their_source_id,
378 "both refer to the same base, so should always match"
379 );
380 let their_rewritten_location = possibly_rewritten_location(
381 pick_our_tree(side, our_tree, their_tree),
382 their_location.as_ref(),
383 pick_our_changes(side, our_changes, their_changes),
384 );
385 let renamed_without_change = their_source_id == their_id;
386 let (merged_blob_id, resolution) = if renamed_without_change {
387 (*our_id, None)
388 } else {
389 let (our_location, our_id, our_mode, their_location, their_id, their_mode) =
390 match side {
391 Original => (
392 our_location,
393 our_id,
394 our_mode,
395 their_location,
396 their_id,
397 their_mode,
398 ),
399 Swapped => (
400 their_location,
401 their_id,
402 their_mode,
403 our_location,
404 our_id,
405 our_mode,
406 ),
407 };
408 let (merged_blob_id, resolution) = perform_blob_merge(
409 labels,
410 objects,
411 blob_merge,
412 &mut diff_state.buf1,
413 &mut write_blob_to_odb,
414 (our_location, *our_id, *our_mode),
415 (their_location, *their_id, *their_mode),
416 (source_location, *previous_id, *previous_entry_mode),
417 (0, outer_side),
418 &options,
419 )?;
420 (merged_blob_id, Some(resolution))
421 };
422
423 editor.remove(toc(our_location))?;
424 pick_our_tree(side, our_tree, their_tree)
425 .remove_existing_leaf(our_location.as_bstr());
426 let final_location = their_rewritten_location.clone();
427 let new_change = Change::Addition {
428 location: their_rewritten_location.unwrap_or_else(|| their_location.to_owned()),
429 relation: None,
430 entry_mode: merged_mode,
431 id: merged_blob_id,
432 };
433 if should_fail_on_conflict(Conflict::with_resolution(
434 Resolution::OursModifiedTheirsRenamedAndChangedThenRename {
435 merged_mode: (merged_mode != *their_mode).then_some(merged_mode),
436 merged_blob: resolution.map(|resolution| ContentMerge {
437 resolution,
438 merged_blob_id,
439 }),
440 final_location,
441 },
442 (ours, theirs, side, outer_side),
443 [
444 index_entry(previous_entry_mode, previous_id),
445 index_entry(our_mode, our_id),
446 index_entry(their_mode, their_id),
447 ],
448 )) {
449 break 'outer;
450 }
451
452 // The other side gets the addition, not our side.
453 push_deferred(
454 (new_change, None),
455 pick_our_changes_mut(side, their_changes, our_changes),
456 );
457 } else {
458 match tree_conflicts {
459 None => {
460 // keep both states - 'our_location' is the previous location as well.
461 editor.upsert(toc(our_location), our_mode.kind(), *our_id)?;
462 editor.upsert(toc(their_location), their_mode.kind(), *their_id)?;
463 }
464 Some(ResolveWith::Ours) => {
465 editor.remove(toc(source_location))?;
466 if side.to_global(outer_side).is_swapped() {
467 editor.upsert(toc(their_location), their_mode.kind(), *their_id)?;
468 } else {
469 editor.upsert(toc(our_location), our_mode.kind(), *our_id)?;
470 }
471 }
472 Some(ResolveWith::Ancestor) => {}
473 }
474
475 if should_fail_on_conflict(Conflict::without_resolution(
476 ResolutionFailure::OursModifiedTheirsRenamedTypeMismatch,
477 (ours, theirs, side, outer_side),
478 [
479 index_entry_at_path(
480 previous_entry_mode,
481 previous_id,
482 ConflictIndexEntryPathHint::RenamedOrTheirs,
483 ),
484 None,
485 index_entry_at_path(
486 their_mode,
487 their_id,
488 ConflictIndexEntryPathHint::RenamedOrTheirs,
489 ),
490 ],
491 )) {
492 break 'outer;
493 }
494 }
495 }
496 (
497 Change::Modification {
498 location,
499 previous_id,
500 previous_entry_mode,
501 entry_mode: our_mode,
502 id: our_id,
503 ..
504 },
505 Change::Modification {
506 entry_mode: their_mode,
507 id: their_id,
508 ..
509 },
510 ) if !involves_submodule(our_mode, their_mode)
511 && merge_modes(*our_mode, *their_mode).is_some()
512 && our_id != their_id =>
513 {
514 let (merged_blob_id, resolution) = perform_blob_merge(
515 labels,
516 objects,
517 blob_merge,
518 &mut diff_state.buf1,
519 &mut write_blob_to_odb,
520 (location, *our_id, *our_mode),
521 (location, *their_id, *their_mode),
522 (location, *previous_id, *previous_entry_mode),
523 (0, outer_side),
524 &options,
525 )?;
526
527 let merged_mode = merge_modes_prev(*our_mode, *their_mode, *previous_entry_mode)
528 .expect("BUG: merge_modes() reports a valid mode, this one should do too");
529
530 editor.upsert(toc(location), merged_mode.kind(), merged_blob_id)?;
531 if should_fail_on_conflict(Conflict::with_resolution(
532 Resolution::OursModifiedTheirsModifiedThenBlobContentMerge {
533 merged_blob: ContentMerge {
534 resolution,
535 merged_blob_id,
536 },
537 },
538 (ours, theirs, Original, outer_side),
539 [
540 index_entry(previous_entry_mode, previous_id),
541 index_entry(our_mode, our_id),
542 index_entry(their_mode, their_id),
543 ],
544 )) {
545 break 'outer;
546 }
547 }
548 (
549 Change::Addition {
550 location,
551 entry_mode: our_mode,
552 id: our_id,
553 ..
554 },
555 Change::Addition {
556 entry_mode: their_mode,
557 id: their_id,
558 ..
559 },
560 ) if !involves_submodule(our_mode, their_mode) && our_id != their_id => {
561 let conflict = if let Some(merged_mode) = merge_modes(*our_mode, *their_mode) {
562 let side = if our_mode == their_mode || matches!(our_mode.kind(), EntryKind::Blob) {
563 outer_side
564 } else {
565 outer_side.swapped()
566 };
567 let (merged_blob_id, resolution) = perform_blob_merge(
568 labels,
569 objects,
570 blob_merge,
571 &mut diff_state.buf1,
572 &mut write_blob_to_odb,
573 (location, *our_id, merged_mode),
574 (location, *their_id, merged_mode),
575 (location, their_id.kind().null(), merged_mode),
576 (0, side),
577 &options,
578 )?;
579 editor.upsert(toc(location), merged_mode.kind(), merged_blob_id)?;
580 Conflict::with_resolution(
581 Resolution::OursModifiedTheirsModifiedThenBlobContentMerge {
582 merged_blob: ContentMerge {
583 resolution,
584 merged_blob_id,
585 },
586 },
587 (ours, theirs, Original, outer_side),
588 [None, index_entry(our_mode, our_id), index_entry(their_mode, their_id)],
589 )
590 } else {
591 // Actually this has a preference, as symlinks are always left in place with the other side renamed.
592 let (
593 logical_side,
594 label_of_side_to_be_moved,
595 (our_mode, our_id, our_path_hint),
596 (their_mode, their_id, their_path_hint),
597 ) = if matches!(our_mode.kind(), EntryKind::Link | EntryKind::Tree) {
598 (
599 Original,
600 labels.other.unwrap_or_default(),
601 (*our_mode, *our_id, ConflictIndexEntryPathHint::Current),
602 (*their_mode, *their_id, ConflictIndexEntryPathHint::RenamedOrTheirs),
603 )
604 } else {
605 (
606 Swapped,
607 labels.current.unwrap_or_default(),
608 (*their_mode, *their_id, ConflictIndexEntryPathHint::RenamedOrTheirs),
609 (*our_mode, *our_id, ConflictIndexEntryPathHint::Current),
610 )
611 };
612 let tree_with_rename = pick_our_tree(logical_side, their_tree, our_tree);
613 let renamed_location = unique_path_in_tree(
614 location.as_bstr(),
615 &editor,
616 tree_with_rename,
617 label_of_side_to_be_moved,
618 )?;
619 let mut conflict = Conflict::without_resolution(
620 ResolutionFailure::OursAddedTheirsAddedTypeMismatch {
621 their_unique_location: renamed_location.clone(),
622 },
623 (ours, theirs, logical_side, outer_side),
624 [
625 None,
626 index_entry_at_path(&our_mode, &our_id, our_path_hint),
627 index_entry_at_path(&their_mode, &their_id, their_path_hint),
628 ],
629 );
630 match tree_conflicts {
631 None => {
632 let new_change = Change::Addition {
633 location: renamed_location,
634 entry_mode: their_mode,
635 id: their_id,
636 relation: None,
637 };
638 editor.upsert(toc(location), our_mode.kind(), our_id)?;
639 tree_with_rename.remove_existing_leaf(location.as_bstr());
640 push_deferred(
641 (new_change, None),
642 pick_our_changes_mut(logical_side, their_changes, our_changes),
643 );
644 }
645 Some(resolve) => {
646 conflict.entries = Default::default();
647 match resolve {
648 ResolveWith::Ours => match outer_side {
649 Original => {
650 editor.upsert(toc(location), our_mode.kind(), our_id)?;
651 }
652 Swapped => {
653 editor.upsert(toc(location), their_mode.kind(), their_id)?;
654 }
655 },
656 ResolveWith::Ancestor => {
657 // Do nothing - this discards both sides.
658 // Note that one of these adds might be the result of a rename, which
659 // means we effectively loose the original and can't get it back as that information is degenerated.
660 }
661 }
662 }
663 }
664 conflict
665 };
666
667 if should_fail_on_conflict(conflict) {
668 break 'outer;
669 }
670 }
671 (
672 Change::Modification {
673 location,
674 entry_mode,
675 id,
676 previous_entry_mode,
677 previous_id,
678 },
679 Change::Deletion { .. },
680 )
681 | (
682 Change::Deletion { .. },
683 Change::Modification {
684 location,
685 entry_mode,
686 id,
687 previous_entry_mode,
688 previous_id,
689 },
690 ) => {
691 let (label_of_side_to_be_moved, side) = if matches!(ours, Change::Modification { .. }) {
692 (labels.current.unwrap_or_default(), Original)
693 } else {
694 (labels.other.unwrap_or_default(), Swapped)
695 };
696 let deletion_prefaces_addition_of_directory = {
697 let change_on_right = match side {
698 Original => their_changes.get(theirs_idx + 1),
699 Swapped => our_changes.get(ours_idx + 1),
700 };
701 change_on_right
702 .map(|change| {
703 change.inner.entry_mode().is_tree()
704 && change.inner.location() == location
705 && matches!(change.inner, Change::Addition { .. })
706 })
707 .unwrap_or_default()
708 };
709
710 let should_break = if deletion_prefaces_addition_of_directory {
711 let entries = [
712 index_entry(previous_entry_mode, previous_id),
713 index_entry(entry_mode, id),
714 None,
715 ];
716 match tree_conflicts {
717 None => {
718 let our_tree = pick_our_tree(side, our_tree, their_tree);
719 let renamed_path = unique_path_in_tree(
720 location.as_bstr(),
721 &editor,
722 our_tree,
723 label_of_side_to_be_moved,
724 )?;
725 editor.remove(toc(location))?;
726 our_tree.remove_existing_leaf(location.as_bstr());
727
728 let new_change = Change::Addition {
729 location: renamed_path.clone(),
730 relation: None,
731 entry_mode: *entry_mode,
732 id: *id,
733 };
734 let should_break = should_fail_on_conflict(Conflict::without_resolution(
735 ResolutionFailure::OursModifiedTheirsDirectoryThenOursRenamed {
736 renamed_unique_path_to_modified_blob: renamed_path,
737 },
738 (ours, theirs, side, outer_side),
739 entries,
740 ));
741
742 // Since we move *our* side, our tree needs to be modified.
743 push_deferred(
744 (new_change, None),
745 pick_our_changes_mut(side, our_changes, their_changes),
746 );
747 should_break
748 }
749 Some(ResolveWith::Ours) => {
750 match side.to_global(outer_side) {
751 Original => {
752 // ours is modification
753 editor.upsert(toc(location), entry_mode.kind(), *id)?;
754 }
755 Swapped => {
756 // ours is deletion
757 editor.remove(toc(location))?;
758 }
759 }
760 should_fail_on_conflict(Conflict::without_resolution(
761 ResolutionFailure::OursModifiedTheirsDeleted,
762 (ours, theirs, side, outer_side),
763 entries,
764 ))
765 }
766 Some(ResolveWith::Ancestor) => {
767 should_fail_on_conflict(Conflict::without_resolution(
768 ResolutionFailure::OursModifiedTheirsDeleted,
769 (ours, theirs, side, outer_side),
770 entries,
771 ))
772 }
773 }
774 } else {
775 let entries = [
776 index_entry(previous_entry_mode, previous_id),
777 index_entry(entry_mode, id),
778 None,
779 ];
780 match tree_conflicts {
781 None => {
782 editor.upsert(toc(location), entry_mode.kind(), *id)?;
783 }
784 Some(ResolveWith::Ours) => {
785 let ours = match outer_side {
786 Original => ours,
787 Swapped => theirs,
788 };
789
790 match ours {
791 Change::Modification { .. } => {
792 editor.upsert(toc(location), entry_mode.kind(), *id)?;
793 }
794 Change::Deletion { .. } => {
795 editor.remove(toc(location))?;
796 }
797 _ => unreachable!("parent-match assures this"),
798 }
799 }
800 Some(ResolveWith::Ancestor) => {}
801 }
802 should_fail_on_conflict(Conflict::without_resolution(
803 ResolutionFailure::OursModifiedTheirsDeleted,
804 (ours, theirs, side, outer_side),
805 entries,
806 ))
807 };
808 if should_break {
809 break 'outer;
810 }
811 }
812 (
813 Change::Modification { .. },
814 Change::Addition {
815 location,
816 entry_mode,
817 id,
818 ..
819 },
820 ) if ours.location() != theirs.location() => {
821 match tree_conflicts {
822 None => {
823 unreachable!("modification/deletion pair should prevent modification/addition from happening")
824 }
825 Some(ResolveWith::Ancestor) => {}
826 Some(ResolveWith::Ours) => {
827 if outer_side.is_swapped() {
828 editor.upsert(toc(location), entry_mode.kind(), *id)?;
829 }
830 // we have already taken care of the 'root' of this -
831 // everything that follows can safely be ignored
832 }
833 }
834 }
835 (
836 Change::Rewrite {
837 source_location,
838 source_entry_mode,
839 source_id,
840 entry_mode: our_mode,
841 id: our_id,
842 location: our_location,
843 ..
844 },
845 Change::Rewrite {
846 entry_mode: their_mode,
847 id: their_id,
848 location: their_location,
849 ..
850 },
851 // NOTE: renames are only tracked among these kinds of types anyway, but we make sure.
852 ) if our_mode.is_blob_or_symlink() && their_mode.is_blob_or_symlink() => {
853 let (merged_blob_id, mut resolution) = if our_id == their_id {
854 (*our_id, None)
855 } else {
856 let (id, resolution) = perform_blob_merge(
857 labels,
858 objects,
859 blob_merge,
860 &mut diff_state.buf1,
861 &mut write_blob_to_odb,
862 (our_location, *our_id, *our_mode),
863 (their_location, *their_id, *their_mode),
864 (source_location, *source_id, *source_entry_mode),
865 (1, outer_side),
866 &options,
867 )?;
868 (id, Some(resolution))
869 };
870
871 let merged_mode =
872 merge_modes(*our_mode, *their_mode).expect("this case was assured earlier");
873
874 if matches!(tree_conflicts, None | Some(ResolveWith::Ours)) {
875 editor.remove(toc(source_location))?;
876 our_tree.remove_existing_leaf(source_location.as_bstr());
877 their_tree.remove_existing_leaf(source_location.as_bstr());
878 }
879
880 let their_location =
881 possibly_rewritten_location(our_tree, their_location.as_bstr(), our_changes)
882 .map_or(Cow::Borrowed(their_location.as_bstr()), Cow::Owned);
883 let our_location =
884 possibly_rewritten_location(their_tree, our_location.as_bstr(), their_changes)
885 .map_or(Cow::Borrowed(our_location.as_bstr()), Cow::Owned);
886 let (our_addition, their_addition) = if our_location == their_location {
887 (
888 None,
889 Some(Change::Addition {
890 location: our_location.into_owned(),
891 relation: None,
892 entry_mode: merged_mode,
893 id: merged_blob_id,
894 }),
895 )
896 } else {
897 if should_fail_on_conflict(Conflict::without_resolution(
898 ResolutionFailure::OursRenamedTheirsRenamedDifferently {
899 merged_blob: resolution.take().map(|resolution| ContentMerge {
900 resolution,
901 merged_blob_id,
902 }),
903 },
904 (ours, theirs, Original, outer_side),
905 [
906 index_entry_at_path(
907 source_entry_mode,
908 source_id,
909 ConflictIndexEntryPathHint::Source,
910 ),
911 index_entry_at_path(
912 our_mode,
913 &merged_blob_id,
914 ConflictIndexEntryPathHint::Current,
915 ),
916 index_entry_at_path(
917 their_mode,
918 &merged_blob_id,
919 ConflictIndexEntryPathHint::RenamedOrTheirs,
920 ),
921 ],
922 )) {
923 break 'outer;
924 }
925 match tree_conflicts {
926 None => {
927 let our_addition = Change::Addition {
928 location: our_location.into_owned(),
929 relation: None,
930 entry_mode: merged_mode,
931 id: merged_blob_id,
932 };
933 let their_addition = Change::Addition {
934 location: their_location.into_owned(),
935 relation: None,
936 entry_mode: merged_mode,
937 id: merged_blob_id,
938 };
939 (Some(our_addition), Some(their_addition))
940 }
941 Some(ResolveWith::Ancestor) => (None, None),
942 Some(ResolveWith::Ours) => {
943 let our_addition = Change::Addition {
944 location: match outer_side {
945 Original => our_location,
946 Swapped => their_location,
947 }
948 .into_owned(),
949 relation: None,
950 entry_mode: merged_mode,
951 id: merged_blob_id,
952 };
953 (Some(our_addition), None)
954 }
955 }
956 };
957
958 if let Some(resolution) = resolution {
959 if should_fail_on_conflict(Conflict::with_resolution(
960 Resolution::OursModifiedTheirsModifiedThenBlobContentMerge {
961 merged_blob: ContentMerge {
962 resolution,
963 merged_blob_id,
964 },
965 },
966 (ours, theirs, Original, outer_side),
967 [
968 index_entry_at_path(
969 source_entry_mode,
970 source_id,
971 ConflictIndexEntryPathHint::Source,
972 ),
973 index_entry_at_path(
974 our_mode,
975 &merged_blob_id,
976 ConflictIndexEntryPathHint::Current,
977 ),
978 index_entry_at_path(
979 their_mode,
980 &merged_blob_id,
981 ConflictIndexEntryPathHint::RenamedOrTheirs,
982 ),
983 ],
984 )) {
985 break 'outer;
986 }
987 }
988 if let Some(addition) = our_addition {
989 push_deferred((addition, Some(ours_idx)), our_changes);
990 }
991 if let Some(addition) = their_addition {
992 push_deferred((addition, Some(theirs_idx)), their_changes);
993 }
994 }
995 (
996 Change::Deletion { .. },
997 Change::Rewrite {
998 source_location,
999 entry_mode: rewritten_mode,
1000 id: rewritten_id,
1001 location,
1002 ..
1003 },
1004 )
1005 | (
1006 Change::Rewrite {
1007 source_location,
1008 entry_mode: rewritten_mode,
1009 id: rewritten_id,
1010 location,
1011 ..
1012 },
1013 Change::Deletion { .. },
1014 ) if !rewritten_mode.is_commit() => {
1015 let side = if matches!(ours, Change::Deletion { .. }) {
1016 Original
1017 } else {
1018 Swapped
1019 };
1020
1021 match tree_conflicts {
1022 None | Some(ResolveWith::Ours) => {
1023 editor.remove(toc(source_location))?;
1024 pick_our_tree(side, our_tree, their_tree)
1025 .remove_existing_leaf(source_location.as_bstr());
1026 }
1027 Some(ResolveWith::Ancestor) => {}
1028 }
1029
1030 let their_rewritten_location = possibly_rewritten_location(
1031 pick_our_tree(side, our_tree, their_tree),
1032 location.as_ref(),
1033 pick_our_changes(side, our_changes, their_changes),
1034 )
1035 .unwrap_or_else(|| location.to_owned());
1036 let our_addition = Change::Addition {
1037 location: their_rewritten_location,
1038 relation: None,
1039 entry_mode: *rewritten_mode,
1040 id: *rewritten_id,
1041 };
1042
1043 if should_fail_on_conflict(Conflict::without_resolution(
1044 ResolutionFailure::OursDeletedTheirsRenamed,
1045 (ours, theirs, side, outer_side),
1046 [
1047 None,
1048 None,
1049 index_entry_at_path(
1050 rewritten_mode,
1051 rewritten_id,
1052 ConflictIndexEntryPathHint::RenamedOrTheirs,
1053 ),
1054 ],
1055 )) {
1056 break 'outer;
1057 }
1058
1059 let ours_is_rewrite = side.is_swapped();
1060 if tree_conflicts.is_none()
1061 || (matches!(tree_conflicts, Some(ResolveWith::Ours)) && ours_is_rewrite)
1062 {
1063 push_deferred(
1064 (our_addition, None),
1065 pick_our_changes_mut(side, their_changes, our_changes),
1066 );
1067 }
1068 }
1069 (
1070 Change::Rewrite {
1071 source_location,
1072 source_entry_mode,
1073 source_id,
1074 entry_mode: our_mode,
1075 id: our_id,
1076 location,
1077 ..
1078 },
1079 Change::Addition {
1080 id: their_id,
1081 entry_mode: their_mode,
1082 location: add_location,
1083 ..
1084 },
1085 )
1086 | (
1087 Change::Addition {
1088 id: their_id,
1089 entry_mode: their_mode,
1090 location: add_location,
1091 ..
1092 },
1093 Change::Rewrite {
1094 source_location,
1095 source_entry_mode,
1096 source_id,
1097 entry_mode: our_mode,
1098 id: our_id,
1099 location,
1100 ..
1101 },
1102 ) if !involves_submodule(our_mode, their_mode) => {
1103 let side = if matches!(ours, Change::Rewrite { .. }) {
1104 Original
1105 } else {
1106 Swapped
1107 };
1108 if let Some(merged_mode) = merge_modes(*our_mode, *their_mode) {
1109 let (merged_blob_id, resolution) = if our_id == their_id {
1110 (*our_id, None)
1111 } else {
1112 let (id, resolution) = perform_blob_merge(
1113 labels,
1114 objects,
1115 blob_merge,
1116 &mut diff_state.buf1,
1117 &mut write_blob_to_odb,
1118 (location, *our_id, *our_mode),
1119 (location, *their_id, *their_mode),
1120 (source_location, source_id.kind().null(), *source_entry_mode),
1121 (0, outer_side),
1122 &options,
1123 )?;
1124 (id, Some(resolution))
1125 };
1126
1127 editor.remove(toc(source_location))?;
1128 pick_our_tree(side, our_tree, their_tree).remove_leaf(source_location.as_bstr());
1129
1130 if let Some(resolution) = resolution {
1131 if should_fail_on_conflict(Conflict::with_resolution(
1132 Resolution::OursModifiedTheirsModifiedThenBlobContentMerge {
1133 merged_blob: ContentMerge {
1134 resolution,
1135 merged_blob_id,
1136 },
1137 },
1138 (ours, theirs, Original, outer_side),
1139 [None, index_entry(our_mode, our_id), index_entry(their_mode, their_id)],
1140 )) {
1141 break 'outer;
1142 }
1143 }
1144
1145 // Because this constellation can only be found by the lookup tree, there is
1146 // no need to put it as addition, we know it's not going to intersect on the other side.
1147 editor.upsert(toc(location), merged_mode.kind(), merged_blob_id)?;
1148 } else {
1149 // We always remove the source from the tree - it might be re-added later.
1150 let ours_is_rename =
1151 tree_conflicts == Some(ResolveWith::Ours) && side == outer_side;
1152 let remove_rename_source =
1153 tree_conflicts.is_none() || ours_is_rename || add_location != source_location;
1154 if remove_rename_source {
1155 editor.remove(toc(source_location))?;
1156 pick_our_tree(side, our_tree, their_tree)
1157 .remove_leaf(source_location.as_bstr());
1158 }
1159
1160 let (
1161 logical_side,
1162 label_of_side_to_be_moved,
1163 (our_mode, our_id, our_path_hint),
1164 (their_mode, their_id, their_path_hint),
1165 ) = if matches!(our_mode.kind(), EntryKind::Link | EntryKind::Tree) {
1166 (
1167 Original,
1168 labels.other.unwrap_or_default(),
1169 (*our_mode, *our_id, ConflictIndexEntryPathHint::Current),
1170 (*their_mode, *their_id, ConflictIndexEntryPathHint::RenamedOrTheirs),
1171 )
1172 } else {
1173 (
1174 Swapped,
1175 labels.current.unwrap_or_default(),
1176 (*their_mode, *their_id, ConflictIndexEntryPathHint::RenamedOrTheirs),
1177 (*our_mode, *our_id, ConflictIndexEntryPathHint::Current),
1178 )
1179 };
1180 let tree_with_rename = pick_our_tree(logical_side, their_tree, our_tree);
1181 let renamed_location = unique_path_in_tree(
1182 location.as_bstr(),
1183 &editor,
1184 tree_with_rename,
1185 label_of_side_to_be_moved,
1186 )?;
1187
1188 let upsert_rename_destination = tree_conflicts.is_none() || ours_is_rename;
1189 if upsert_rename_destination {
1190 editor.upsert(toc(location), our_mode.kind(), our_id)?;
1191 tree_with_rename.remove_existing_leaf(location.as_bstr());
1192 }
1193
1194 let conflict = Conflict::without_resolution(
1195 ResolutionFailure::OursAddedTheirsAddedTypeMismatch {
1196 their_unique_location: renamed_location.clone(),
1197 },
1198 (ours, theirs, side, outer_side),
1199 [
1200 None,
1201 index_entry_at_path(&our_mode, &our_id, our_path_hint),
1202 index_entry_at_path(&their_mode, &their_id, their_path_hint),
1203 ],
1204 );
1205
1206 if tree_conflicts.is_none() {
1207 let new_change_with_rename = Change::Addition {
1208 location: renamed_location,
1209 entry_mode: their_mode,
1210 id: their_id,
1211 relation: None,
1212 };
1213 push_deferred(
1214 (
1215 new_change_with_rename,
1216 Some(pick_idx(logical_side, theirs_idx, ours_idx)),
1217 ),
1218 pick_our_changes_mut(logical_side, their_changes, our_changes),
1219 );
1220 }
1221
1222 if should_fail_on_conflict(conflict) {
1223 break 'outer;
1224 }
1225 }
1226 }
1227 _unknown => {
1228 debug_assert!(
1229 match_kind.is_none()
1230 || (ours.location() == theirs.location()
1231 || ours.source_location() == theirs.source_location()),
1232 "BUG: right now it's not known to be possible to match changes from different paths: {match_kind:?} {candidate:?}"
1233 );
1234 if let Some(ResolveWith::Ours) = tree_conflicts {
1235 apply_our_resolution(ours, theirs, outer_side, &mut editor)?;
1236 }
1237 if should_fail_on_conflict(Conflict::unknown((ours, theirs, Original, outer_side))) {
1238 break 'outer;
1239 }
1240 }
1241 }
1242 their_changes[theirs_idx].was_written = true;
1243 our_changes[ours_idx].was_written = true;
1244 }
1245 }
1246 }
1247 segment_start = last_seen_len;
1248 last_seen_len = their_changes.len();
1249 }
1250
1251 ((our_changes, our_tree), (their_changes, their_tree)) = ((their_changes, their_tree), (our_changes, our_tree));
1252 (labels.current, labels.other) = (labels.other, labels.current);
1253 outer_side = outer_side.swapped();
1254 }
1255
1256 Ok(Outcome {
1257 tree: editor,
1258 conflicts,
1259 failed_on_first_unresolved_conflict: failed_on_first_conflict,
1260 })
1261}
1262
1263fn apply_our_resolution(
1264 local_ours: &Change,
1265 local_theirs: &Change,
1266 outer_side: ConflictMapping,
1267 editor: &mut gix_object::tree::Editor<'_>,
1268) -> Result<(), Error> {
1269 let ours = match outer_side {
1270 Original => local_ours,
1271 Swapped => local_theirs,
1272 };
1273 Ok(apply_change(editor, ours, None)?)
1274}
1275
1276fn involves_submodule(a: &EntryMode, b: &EntryMode) -> bool {
1277 a.is_commit() || b.is_commit()
1278}
1279
1280/// Allows equal modes or prefers executables bits in case of blobs
1281///
1282/// Note that this is often not correct as the previous mode of each side should be taken into account so that:
1283///
1284/// on | on = on
1285/// off | off = off
1286/// on | off || off | on = conflict
1287fn merge_modes(a: EntryMode, b: EntryMode) -> Option<EntryMode> {
1288 match (a.kind(), b.kind()) {
1289 (_, _) if a == b => Some(a),
1290 (EntryKind::BlobExecutable, EntryKind::BlobExecutable | EntryKind::Blob)
1291 | (EntryKind::Blob, EntryKind::BlobExecutable) => Some(EntryKind::BlobExecutable.into()),
1292 _ => None,
1293 }
1294}
1295
1296/// Use this version if there is a single common `prev` value for both `a` and `b` to detect
1297/// if the mode was turned on or off.
1298fn merge_modes_prev(a: EntryMode, b: EntryMode, prev: EntryMode) -> Option<EntryMode> {
1299 match (a.kind(), b.kind()) {
1300 (_, _) if a == b => Some(a),
1301 (a @ EntryKind::BlobExecutable, b @ (EntryKind::BlobExecutable | EntryKind::Blob))
1302 | (a @ EntryKind::Blob, b @ EntryKind::BlobExecutable) => {
1303 let prev = prev.kind();
1304 let changed = if a == prev { b } else { a };
1305 Some(
1306 match (prev, changed) {
1307 (EntryKind::Blob, EntryKind::BlobExecutable) => EntryKind::BlobExecutable,
1308 (EntryKind::BlobExecutable, EntryKind::Blob) => EntryKind::Blob,
1309 _ => unreachable!("upper match already assured we only deal with blobs"),
1310 }
1311 .into(),
1312 )
1313 }
1314 _ => None,
1315 }
1316}
1317
1318fn push_deferred(change_and_idx: (Change, Option<usize>), changes: &mut ChangeList) {
1319 push_deferred_with_rewrite(change_and_idx, None, changes);
1320}
1321
1322fn push_deferred_with_rewrite(
1323 (change, ours_idx): (Change, Option<usize>),
1324 new_location: Option<(BString, usize)>,
1325 changes: &mut ChangeList,
1326) {
1327 changes.push(TrackedChange {
1328 inner: change,
1329 was_written: false,
1330 needs_tree_insertion: Some(ours_idx),
1331 rewritten_location: new_location,
1332 });
1333}
1334
1335fn pick_our_tree<'a>(side: ConflictMapping, ours: &'a mut TreeNodes, theirs: &'a mut TreeNodes) -> &'a mut TreeNodes {
1336 match side {
1337 Original => ours,
1338 Swapped => theirs,
1339 }
1340}
1341
1342fn pick_our_changes<'a>(
1343 side: ConflictMapping,
1344 ours: &'a ChangeListRef,
1345 theirs: &'a ChangeListRef,
1346) -> &'a ChangeListRef {
1347 match side {
1348 Original => ours,
1349 Swapped => theirs,
1350 }
1351}
1352
1353fn pick_idx(side: ConflictMapping, ours: usize, theirs: usize) -> usize {
1354 match side {
1355 Original => ours,
1356 Swapped => theirs,
1357 }
1358}
1359
1360fn pick_our_changes_mut<'a>(
1361 side: ConflictMapping,
1362 ours: &'a mut ChangeList,
1363 theirs: &'a mut ChangeList,
1364) -> &'a mut ChangeList {
1365 match side {
1366 Original => ours,
1367 Swapped => theirs,
1368 }
1369}
1370
1371fn index_entry(mode: &gix_object::tree::EntryMode, id: &gix_hash::ObjectId) -> Option<ConflictIndexEntry> {
1372 Some(ConflictIndexEntry {
1373 mode: *mode,
1374 id: *id,
1375 path_hint: None,
1376 })
1377}
1378
1379fn index_entry_at_path(
1380 mode: &gix_object::tree::EntryMode,
1381 id: &gix_hash::ObjectId,
1382 hint: ConflictIndexEntryPathHint,
1383) -> Option<ConflictIndexEntry> {
1384 Some(ConflictIndexEntry {
1385 mode: *mode,
1386 id: *id,
1387 path_hint: Some(hint),
1388 })
1389}