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