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}