gix_merge/tree/
mod.rs

1use bstr::BString;
2use gix_diff::{tree_with_rewrites::Change, Rewrites};
3
4/// The error returned by [`tree()`](crate::tree()).
5#[derive(Debug, thiserror::Error)]
6#[allow(missing_docs)]
7pub enum Error {
8    #[error("Could not find ancestor, our or their tree to get started")]
9    FindTree(#[from] gix_object::find::existing_object::Error),
10    #[error("Could not find ancestor, our or their tree iterator to get started")]
11    FindTreeIter(#[from] gix_object::find::existing_iter::Error),
12    #[error("Failed to diff our side or their side")]
13    DiffTree(#[from] gix_diff::tree_with_rewrites::Error),
14    #[error("Could not apply merge result to base tree")]
15    TreeEdit(#[from] gix_object::tree::editor::Error),
16    #[error("Failed to load resource to prepare for blob merge")]
17    BlobMergeSetResource(#[from] crate::blob::platform::set_resource::Error),
18    #[error(transparent)]
19    BlobMergePrepare(#[from] crate::blob::platform::prepare_merge::Error),
20    #[error(transparent)]
21    BlobMerge(#[from] crate::blob::platform::merge::Error),
22    #[error("Failed to write merged blob content as blob to the object database")]
23    WriteBlobToOdb(Box<dyn std::error::Error + Send + Sync + 'static>),
24    #[error("The merge was performed, but the binary merge result couldn't be selected as it wasn't found")]
25    MergeResourceNotFound,
26}
27
28/// The outcome produced by [`tree()`](crate::tree()).
29#[derive(Clone)]
30pub struct Outcome<'a> {
31    /// The ready-made (but unwritten) *base* tree, including all non-conflicting changes, and the changes that had
32    /// conflicts which could be resolved automatically.
33    ///
34    /// This means, if all of their changes were conflicting, this will be equivalent to the *base* tree.
35    pub tree: gix_object::tree::Editor<'a>,
36    /// The set of conflicts we encountered. Can be empty to indicate there was no conflict.
37    /// Note that conflicts might have been auto-resolved, but they are listed here for completeness.
38    /// Use [`has_unresolved_conflicts()`](Outcome::has_unresolved_conflicts()) to see if any action is needed
39    /// before using [`tree`](Outcome::tree).
40    pub conflicts: Vec<Conflict>,
41    /// `true` if `conflicts` contains only a single [*unresolved* conflict](ResolutionFailure) in the last slot, but
42    /// possibly more [resolved ones](Resolution) before that.
43    /// This also makes this outcome a very partial merge that cannot be completed.
44    /// Only set if [`fail_on_conflict`](Options::fail_on_conflict) is `true`.
45    pub failed_on_first_unresolved_conflict: bool,
46}
47
48/// Determine what should be considered an unresolved conflict.
49#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
50pub struct TreatAsUnresolved {
51    /// Determine which content merges should be considered unresolved.
52    pub content_merge: treat_as_unresolved::ContentMerge,
53    /// Determine which tree merges should be considered unresolved.
54    pub tree_merge: treat_as_unresolved::TreeMerge,
55}
56
57///
58pub mod treat_as_unresolved {
59    use crate::tree::TreatAsUnresolved;
60
61    /// Which kind of content merges should be considered unresolved?
62    #[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
63    pub enum ContentMerge {
64        /// Content merges that still show conflict markers.
65        #[default]
66        Markers,
67        /// Content merges who would have conflicted if it wasn't for a
68        /// [resolution strategy](crate::blob::builtin_driver::text::Conflict::ResolveWithOurs).
69        ForcedResolution,
70    }
71
72    /// Which kind of tree merges should be considered unresolved?
73    #[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
74    pub enum TreeMerge {
75        /// All failed renames.
76        Undecidable,
77        /// All failed renames, and the ones where a tree item was renamed to avoid a clash.
78        #[default]
79        EvasiveRenames,
80        /// All of `EvasiveRenames`, and tree merges that would have conflicted but which were resolved
81        /// with a [resolution strategy](super::ResolveWith).
82        ForcedResolution,
83    }
84
85    /// Instantiation/Presets
86    impl TreatAsUnresolved {
87        /// Return an instance with the highest sensitivity to what should be considered unresolved as it
88        /// includes entries which have been resolved using a [merge strategy](super::ResolveWith).
89        pub fn forced_resolution() -> Self {
90            Self {
91                content_merge: ContentMerge::ForcedResolution,
92                tree_merge: TreeMerge::ForcedResolution,
93            }
94        }
95
96        /// Return an instance that considers unresolved any conflict that Git would also consider unresolved.
97        /// This is the same as the `default()` implementation.
98        pub fn git() -> Self {
99            Self::default()
100        }
101
102        /// Only undecidable tree merges and conflict markers are considered unresolved.
103        /// This also means that renamed entries to make space for a conflicting one is considered acceptable,
104        /// making this preset the most lenient.
105        pub fn undecidable() -> Self {
106            Self {
107                content_merge: ContentMerge::Markers,
108                tree_merge: TreeMerge::Undecidable,
109            }
110        }
111    }
112}
113
114impl Outcome<'_> {
115    /// Return `true` if there is any conflict that would still need to be resolved as they would yield undesirable trees.
116    /// This is based on `how` to determine what should be considered unresolved.
117    pub fn has_unresolved_conflicts(&self, how: TreatAsUnresolved) -> bool {
118        self.conflicts.iter().any(|c| c.is_unresolved(how))
119    }
120
121    /// Returns `true` if `index` changed as we applied conflicting stages to it, using `how` to determine if a
122    /// conflict should be considered unresolved.
123    /// `removal_mode` decides how unconflicted entries should be removed if they are superseded by
124    /// their conflicted counterparts.
125    /// It's important that `index` is at the state of [`Self::tree`].
126    ///
127    /// Note that in practice, whenever there is a single [conflict](Conflict), this function will return `true`.
128    pub fn index_changed_after_applying_conflicts(
129        &self,
130        index: &mut gix_index::State,
131        how: TreatAsUnresolved,
132        removal_mode: apply_index_entries::RemovalMode,
133    ) -> bool {
134        apply_index_entries(&self.conflicts, how, index, removal_mode)
135    }
136}
137
138/// A description of a conflict (i.e. merge issue without an auto-resolution) as seen during a [tree-merge](crate::tree()).
139/// They may have a resolution that was applied automatically, or be left for the caller to resolved.
140#[derive(Debug, Clone)]
141pub struct Conflict {
142    /// A record on how the conflict resolution succeeded with `Ok(_)` or failed with `Err(_)`.
143    /// Note that in case of `Err(_)`, edits may still have been made to the tree to aid resolution.
144    /// On failure, one can examine `ours` and `theirs` to potentially find a custom solution.
145    /// Note that the descriptions of resolutions or resolution failures may be swapped compared
146    /// to the actual changes. This is due to changes like `modification|deletion` being treated the
147    /// same as `deletion|modification`, i.e. *ours* is not more privileged than theirs.
148    /// To compensate for that, use [`changes_in_resolution()`](Conflict::changes_in_resolution()).
149    pub resolution: Result<Resolution, ResolutionFailure>,
150    /// The change representing *our* side.
151    pub ours: Change,
152    /// The change representing *their* side.
153    pub theirs: Change,
154    /// An array to store an entry for each stage of the conflict.
155    ///
156    /// * `entries[0]`  => Base
157    /// * `entries[1]`  => Ours
158    /// * `entries[2]`  => Theirs
159    ///
160    /// Note that ours and theirs might be swapped, so one should access it through [`Self::entries()`] to compensate for that.
161    pub entries: [Option<ConflictIndexEntry>; 3],
162    /// Determine how to interpret the `ours` and `theirs` fields. This is used to implement [`Self::changes_in_resolution()`]
163    /// and [`Self::into_parts_by_resolution()`].
164    map: ConflictMapping,
165}
166
167/// A conflicting entry for insertion into the index.
168/// It will always be either on stage 1 (ancestor/base), 2 (ours) or 3 (theirs)
169#[derive(Debug, Clone, Copy)]
170pub struct ConflictIndexEntry {
171    /// The kind of object at this stage.
172    /// Note that it's possible that this is a directory, for instance if a directory was replaced with a file.
173    pub mode: gix_object::tree::EntryMode,
174    /// The id defining the state of the object.
175    pub id: gix_hash::ObjectId,
176    /// Hidden, maybe one day we can do without?
177    path_hint: Option<ConflictIndexEntryPathHint>,
178}
179
180/// A hint for [`apply_index_entries()`] to know which paths to use for an entry.
181/// This is only used when necessary.
182#[derive(Debug, Clone, Copy)]
183enum ConflictIndexEntryPathHint {
184    /// Use the previous path, i.e. rename source.
185    Source,
186    /// Use the current path as it is in the tree.
187    Current,
188    /// Use the path of the final destination, or *their* name.
189    /// It's definitely finicky, as we don't store the actual path and instead refer to it.
190    RenamedOrTheirs,
191}
192
193/// A utility to help define which side is what in the [`Conflict`] type.
194#[derive(Debug, Clone, Copy, Eq, PartialEq)]
195enum ConflictMapping {
196    /// The sides are as described in the field documentation, i.e. `ours` is `ours`.
197    Original,
198    /// The sides are the opposite of the field documentation. i.e. `ours` is `theirs` and `theirs` is `ours`.
199    Swapped,
200}
201
202impl ConflictMapping {
203    fn is_swapped(&self) -> bool {
204        matches!(self, ConflictMapping::Swapped)
205    }
206    fn swapped(self) -> ConflictMapping {
207        match self {
208            ConflictMapping::Original => ConflictMapping::Swapped,
209            ConflictMapping::Swapped => ConflictMapping::Original,
210        }
211    }
212    fn to_global(self, global: ConflictMapping) -> ConflictMapping {
213        match global {
214            ConflictMapping::Original => self,
215            ConflictMapping::Swapped => self.swapped(),
216        }
217    }
218}
219
220impl Conflict {
221    /// Return `true` if this instance is considered unresolved based on the criterion specified by `how`.
222    pub fn is_unresolved(&self, how: TreatAsUnresolved) -> bool {
223        use crate::blob;
224        let content_merge_unresolved = |info: &ContentMerge| match how.content_merge {
225            treat_as_unresolved::ContentMerge::Markers => matches!(info.resolution, blob::Resolution::Conflict),
226            treat_as_unresolved::ContentMerge::ForcedResolution => {
227                matches!(
228                    info.resolution,
229                    blob::Resolution::Conflict | blob::Resolution::CompleteWithAutoResolvedConflict
230                )
231            }
232        };
233        match how.tree_merge {
234            treat_as_unresolved::TreeMerge::Undecidable => {
235                self.resolution.is_err() || self.content_merge().is_some_and(|info| content_merge_unresolved(&info))
236            }
237            treat_as_unresolved::TreeMerge::EvasiveRenames | treat_as_unresolved::TreeMerge::ForcedResolution => {
238                match &self.resolution {
239                    Ok(success) => match success {
240                        Resolution::SourceLocationAffectedByRename { .. } => false,
241                        Resolution::Forced(_) => {
242                            how.tree_merge == treat_as_unresolved::TreeMerge::ForcedResolution
243                                || self
244                                    .content_merge()
245                                    .is_some_and(|merged_blob| content_merge_unresolved(&merged_blob))
246                        }
247                        Resolution::OursModifiedTheirsRenamedAndChangedThenRename {
248                            merged_blob,
249                            final_location,
250                            ..
251                        } => final_location.is_some() || merged_blob.as_ref().is_some_and(content_merge_unresolved),
252                        Resolution::OursModifiedTheirsModifiedThenBlobContentMerge { merged_blob } => {
253                            content_merge_unresolved(merged_blob)
254                        }
255                    },
256                    Err(_failure) => true,
257                }
258            }
259        }
260    }
261
262    /// Returns the changes of fields `ours` and `theirs` so they match their description in the
263    /// [`Resolution`] or [`ResolutionFailure`] respectively.
264    /// Without this, the sides may appear swapped as `ours|theirs` is treated the same as `theirs/ours`
265    /// if both types are different, like `modification|deletion`.
266    pub fn changes_in_resolution(&self) -> (&Change, &Change) {
267        match self.map {
268            ConflictMapping::Original => (&self.ours, &self.theirs),
269            ConflictMapping::Swapped => (&self.theirs, &self.ours),
270        }
271    }
272
273    /// Similar to [`changes_in_resolution()`](Self::changes_in_resolution()), but returns the parts
274    /// of the structure so the caller can take ownership. This can be useful when applying your own
275    /// resolutions for resolution failures.
276    pub fn into_parts_by_resolution(self) -> (Result<Resolution, ResolutionFailure>, Change, Change) {
277        match self.map {
278            ConflictMapping::Original => (self.resolution, self.ours, self.theirs),
279            ConflictMapping::Swapped => (self.resolution, self.theirs, self.ours),
280        }
281    }
282
283    /// Return the index entries for insertion into the index, to match with what's returned by [`Self::changes_in_resolution()`].
284    pub fn entries(&self) -> [Option<ConflictIndexEntry>; 3] {
285        match self.map {
286            ConflictMapping::Original => self.entries,
287            ConflictMapping::Swapped => [self.entries[0], self.entries[2], self.entries[1]],
288        }
289    }
290
291    /// Return information about the content merge if it was performed.
292    pub fn content_merge(&self) -> Option<ContentMerge> {
293        fn failure_merged_blob(failure: &ResolutionFailure) -> Option<ContentMerge> {
294            match failure {
295                ResolutionFailure::OursRenamedTheirsRenamedDifferently { merged_blob } => *merged_blob,
296                ResolutionFailure::Unknown
297                | ResolutionFailure::OursDirectoryTheirsNonDirectoryTheirsRenamed { .. }
298                | ResolutionFailure::OursModifiedTheirsDeleted
299                | ResolutionFailure::OursModifiedTheirsRenamedTypeMismatch
300                | ResolutionFailure::OursModifiedTheirsDirectoryThenOursRenamed {
301                    renamed_unique_path_to_modified_blob: _,
302                }
303                | ResolutionFailure::OursAddedTheirsAddedTypeMismatch { .. }
304                | ResolutionFailure::OursDeletedTheirsRenamed => None,
305            }
306        }
307        match &self.resolution {
308            Ok(success) => match success {
309                Resolution::Forced(failure) => failure_merged_blob(failure),
310                Resolution::SourceLocationAffectedByRename { .. } => None,
311                Resolution::OursModifiedTheirsRenamedAndChangedThenRename { merged_blob, .. } => *merged_blob,
312                Resolution::OursModifiedTheirsModifiedThenBlobContentMerge { merged_blob } => Some(*merged_blob),
313            },
314            Err(failure) => failure_merged_blob(failure),
315        }
316    }
317}
318
319/// Describes of a conflict involving *our* change and *their* change was specifically resolved.
320///
321/// Note that all resolutions are side-agnostic, so *ours* could also have been *theirs* and vice versa.
322/// Also note that symlink merges are always done via binary merge, using the same logic.
323#[derive(Debug, Clone)]
324pub enum Resolution {
325    /// *ours* had a renamed directory and *theirs* made a change in the now renamed directory.
326    /// We moved that change into its location.
327    SourceLocationAffectedByRename {
328        /// The repository-relative path to the location that the change ended up in after
329        /// being affected by a renamed directory.
330        final_location: BString,
331    },
332    /// *ours* was a modified blob and *theirs* renamed that blob.
333    /// We moved the changed blob from *ours* to its new location, and merged it successfully.
334    /// If this is a `copy`, the source of the copy was set to be the changed blob as well so both match.
335    OursModifiedTheirsRenamedAndChangedThenRename {
336        /// If one side added the executable bit, we always add it in the merged result.
337        merged_mode: Option<gix_object::tree::EntryMode>,
338        /// If `Some(…)`, the content of the involved blob had to be merged.
339        merged_blob: Option<ContentMerge>,
340        /// The repository relative path to the location the blob finally ended up in.
341        /// It's `Some()` only if *they* rewrote the blob into a directory which *we* renamed on *our* side.
342        final_location: Option<BString>,
343    },
344    /// *ours* and *theirs* carried changes and where content-merged.
345    ///
346    /// Note that *ours* and *theirs* may also be rewrites with the same destination and mode,
347    /// or additions.
348    OursModifiedTheirsModifiedThenBlobContentMerge {
349        /// The outcome of the content merge.
350        merged_blob: ContentMerge,
351    },
352    /// This is a resolution failure was forcefully turned into a usable resolution, i.e. [making a choice](ResolveWith)
353    /// is turned into a valid resolution.
354    Forced(ResolutionFailure),
355}
356
357/// Describes of a conflict involving *our* change and *their* failed to be resolved.
358#[derive(Debug, Clone)]
359pub enum ResolutionFailure {
360    /// *ours* was renamed, but *theirs* was renamed differently. Both versions will be present in the tree,
361    OursRenamedTheirsRenamedDifferently {
362        /// If `Some(…)`, the content of the involved blob had to be merged.
363        merged_blob: Option<ContentMerge>,
364    },
365    /// *ours* was modified, but *theirs* was turned into a directory, so *ours* was renamed to a non-conflicting path.
366    OursModifiedTheirsDirectoryThenOursRenamed {
367        /// The path at which `ours` can be found in the tree - it's in the same directory that it was in before.
368        renamed_unique_path_to_modified_blob: BString,
369    },
370    /// *ours* is a directory, but *theirs* is a non-directory (i.e. file), which wants to be in its place, even though
371    /// *ours* has a modification in that subtree.
372    /// Rename *theirs* to retain that modification.
373    ///
374    /// Important: there is no actual modification on *ours* side, so *ours* is filled in with *theirs* as the data structure
375    /// cannot represent this case.
376    // TODO: Can we have a better data-structure? This would be for a rewrite though.
377    OursDirectoryTheirsNonDirectoryTheirsRenamed {
378        /// The non-conflicting path of *their* non-tree entry.
379        renamed_unique_path_of_theirs: BString,
380    },
381    /// *ours* was added (or renamed into place) with a different mode than theirs, e.g. blob and symlink, and we kept
382    /// the symlink in its original location, renaming the other side to `their_unique_location`.
383    OursAddedTheirsAddedTypeMismatch {
384        /// The location at which *their* state was placed to resolve the name and type clash, named to indicate
385        /// where the entry is coming from.
386        their_unique_location: BString,
387    },
388    /// *ours* was modified, and they renamed the same file, but there is also a non-mergable type-change.
389    /// Here we keep both versions of the file.
390    OursModifiedTheirsRenamedTypeMismatch,
391    /// *ours* was deleted, but *theirs* was renamed.
392    OursDeletedTheirsRenamed,
393    /// *ours* was modified and *theirs* was deleted. We keep the modified one and ignore the deletion.
394    OursModifiedTheirsDeleted,
395    /// *ours* and *theirs* are in an untested state so it can't be handled yet, and is considered a conflict
396    /// without adding our *or* their side to the resulting tree.
397    Unknown,
398}
399
400/// Information about a blob content merge for use in a [`Resolution`].
401/// Note that content merges always count as success to avoid duplication of cases, which forces callers
402/// to check for the [`resolution`](Self::resolution) field.
403#[derive(Debug, Copy, Clone)]
404pub struct ContentMerge {
405    /// The fully merged blob.
406    pub merged_blob_id: gix_hash::ObjectId,
407    /// Identify the kind of resolution of the blob merge. Note that it may be conflicting.
408    pub resolution: crate::blob::Resolution,
409}
410
411/// A way to configure [`tree()`](crate::tree()).
412#[derive(Default, Debug, Clone)]
413pub struct Options {
414    /// If *not* `None`, rename tracking will be performed when determining the changes of each side of the merge.
415    ///
416    /// Note that [empty blobs](Rewrites::track_empty) should not be tracked for best results.
417    pub rewrites: Option<Rewrites>,
418    /// Decide how blob-merges should be done. This relates to if conflicts can be resolved or not.
419    pub blob_merge: crate::blob::platform::merge::Options,
420    /// The context to use when invoking merge-drivers.
421    pub blob_merge_command_ctx: gix_command::Context,
422    /// If `Some(what-is-unresolved)`, the first unresolved conflict will cause the entire merge to stop.
423    /// This is useful to see if there is any conflict, without performing the whole operation, something
424    /// that can be very relevant during merges that would cause a lot of blob-diffs.
425    pub fail_on_conflict: Option<TreatAsUnresolved>,
426    /// This value also affects the size of merge-conflict markers, to allow differentiating
427    /// merge conflicts on each level, for any value greater than 0, with values `N` causing `N*2`
428    /// markers to be added to the configured value.
429    ///
430    /// This is used automatically when merging merge-bases recursively.
431    pub marker_size_multiplier: u8,
432    /// If `None`, when symlinks clash *ours* will be chosen and a conflict will occur.
433    /// Otherwise, the same logic applies as for the merge of binary resources.
434    pub symlink_conflicts: Option<crate::blob::builtin_driver::binary::ResolveWith>,
435    /// If `None`, tree irreconcilable tree conflicts will result in [resolution failures](ResolutionFailure).
436    /// Otherwise, one can choose a side. Note that it's still possible to determine that auto-resolution happened
437    /// despite this choice, which allows carry forward the conflicting information, possibly for later resolution.
438    /// If `Some(…)`, irreconcilable conflicts are reconciled by making a choice.
439    /// Note that [`Conflict::entries()`] will still be set, to not degenerate information, even though they then represent
440    /// the entries what would fit the index if no forced resolution was performed.
441    /// It's up to the caller to handle that information mindfully.
442    pub tree_conflicts: Option<ResolveWith>,
443}
444
445/// Decide how to resolve tree-related conflicts, but only those that have [no way of being correct](ResolutionFailure).
446#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
447pub enum ResolveWith {
448    /// On irreconcilable conflict, choose neither *our* nor *their* state, but keep the common *ancestor* state instead.
449    Ancestor,
450    /// On irreconcilable conflict, choose *our* side.
451    ///
452    /// Note that in order to get something equivalent to *theirs*, put *theirs* into the side of *ours*,
453    /// swapping the sides essentially.
454    Ours,
455}
456
457pub(super) mod function;
458mod utils;
459///
460pub mod apply_index_entries {
461
462    /// Determines how we deal with the removal of unconflicted entries if these are superseded by their conflicted counterparts,
463    /// i.e. stage 1, 2 and 3.
464    #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
465    pub enum RemovalMode {
466        /// Add the [`gix_index::entry::Flags::REMOVE`] flag to entries that are to be removed.
467        ///
468        /// **Note** that this also means that unconflicted and conflicted stages will be visible in the same index.
469        /// When written, entries marked for removal will automatically be ignored. However, this also means that
470        /// one must not use the in-memory index or take specific care of entries that are marked for removal.
471        Mark,
472        /// Entries marked for removal (even those that were already marked) will be removed from memory at the end.
473        ///
474        /// This is an expensive step that leaves a consistent index, ready for use.
475        Prune,
476    }
477
478    pub(super) mod function {
479        use std::collections::{hash_map, HashMap};
480
481        use bstr::{BStr, ByteSlice};
482
483        use crate::tree::{
484            apply_index_entries::RemovalMode, Conflict, ConflictIndexEntryPathHint, Resolution, ResolutionFailure,
485            TreatAsUnresolved,
486        };
487
488        /// Returns `true` if `index` changed as we applied conflicting stages to it, using `how` to determine if a
489        /// conflict should be considered unresolved.
490        /// Once a stage of a path conflicts, the unconflicting stage is removed even though it might be the one
491        /// that is currently checked out.
492        /// This removal is only done by flagging it with [gix_index::entry::Flags::REMOVE], which means
493        /// these entries won't be written back to disk but will still be present in the index if `removal_mode`
494        /// is [`RemovalMode::Mark`]. For proper removal, choose [`RemovalMode::Prune`].
495        /// It's important that `index` matches the tree that was produced as part of the merge that also
496        /// brought about `conflicts`, or else this function will fail if it cannot find the path matching
497        /// the conflicting entries.
498        ///
499        /// Note that in practice, whenever there is a single [conflict](Conflict), this function will return `true`.
500        /// Errors can only occour if `index` isn't the one created from the merged tree that produced the `conflicts`.
501        pub fn apply_index_entries(
502            conflicts: &[Conflict],
503            how: TreatAsUnresolved,
504            index: &mut gix_index::State,
505            removal_mode: RemovalMode,
506        ) -> bool {
507            if index.is_sparse() {
508                gix_trace::error!("Refusing to apply index entries to sparse index - it's not tested yet");
509                return false;
510            }
511            let len = index.entries().len();
512            let mut idx_by_path_stage = HashMap::<(gix_index::entry::Stage, &BStr), usize>::default();
513            for conflict in conflicts.iter().filter(|c| c.is_unresolved(how)) {
514                let (renamed_path, current_path): (Option<&BStr>, &BStr) = match &conflict.resolution {
515                    Ok(success) => match success {
516                        Resolution::Forced(_) => continue,
517                        Resolution::SourceLocationAffectedByRename { final_location } => {
518                            (Some(final_location.as_bstr()), final_location.as_bstr())
519                        }
520                        Resolution::OursModifiedTheirsRenamedAndChangedThenRename { final_location, .. } => (
521                            final_location.as_ref().map(|p| p.as_bstr()),
522                            conflict.changes_in_resolution().1.location(),
523                        ),
524                        Resolution::OursModifiedTheirsModifiedThenBlobContentMerge { .. } => {
525                            (None, conflict.ours.location())
526                        }
527                    },
528                    Err(failure) => match failure {
529                        ResolutionFailure::OursDirectoryTheirsNonDirectoryTheirsRenamed {
530                            renamed_unique_path_of_theirs,
531                        } => (Some(renamed_unique_path_of_theirs.as_bstr()), conflict.ours.location()),
532                        ResolutionFailure::OursRenamedTheirsRenamedDifferently { .. } => {
533                            (Some(conflict.theirs.location()), conflict.ours.location())
534                        }
535                        ResolutionFailure::OursModifiedTheirsRenamedTypeMismatch
536                        | ResolutionFailure::OursDeletedTheirsRenamed
537                        | ResolutionFailure::OursModifiedTheirsDeleted
538                        | ResolutionFailure::Unknown => (None, conflict.ours.location()),
539                        ResolutionFailure::OursModifiedTheirsDirectoryThenOursRenamed {
540                            renamed_unique_path_to_modified_blob,
541                        } => (
542                            Some(renamed_unique_path_to_modified_blob.as_bstr()),
543                            conflict.ours.location(),
544                        ),
545                        ResolutionFailure::OursAddedTheirsAddedTypeMismatch { their_unique_location } => {
546                            (Some(their_unique_location.as_bstr()), conflict.ours.location())
547                        }
548                    },
549                };
550                let source_path = conflict.ours.source_location();
551
552                let entries_with_stage = conflict.entries().into_iter().enumerate().filter_map(|(idx, entry)| {
553                    entry.filter(|e| e.mode.is_no_tree()).map(|e| {
554                        (
555                            match idx {
556                                0 => gix_index::entry::Stage::Base,
557                                1 => gix_index::entry::Stage::Ours,
558                                2 => gix_index::entry::Stage::Theirs,
559                                _ => unreachable!("fixed size array with three items"),
560                            },
561                            match e.path_hint {
562                                None => renamed_path.unwrap_or(current_path),
563                                Some(ConflictIndexEntryPathHint::Source) => source_path,
564                                Some(ConflictIndexEntryPathHint::Current) => current_path,
565                                Some(ConflictIndexEntryPathHint::RenamedOrTheirs) => {
566                                    renamed_path.unwrap_or_else(|| conflict.changes_in_resolution().1.location())
567                                }
568                            },
569                            e,
570                        )
571                    })
572                });
573
574                if !entries_with_stage.clone().any(|(_, path, _)| {
575                    index
576                        .entry_index_by_path_and_stage_bounded(path, gix_index::entry::Stage::Unconflicted, len)
577                        .is_some()
578                }) {
579                    continue;
580                }
581
582                for (stage, path, entry) in entries_with_stage {
583                    if let Some(pos) =
584                        index.entry_index_by_path_and_stage_bounded(path, gix_index::entry::Stage::Unconflicted, len)
585                    {
586                        index.entries_mut()[pos].flags.insert(gix_index::entry::Flags::REMOVE);
587                    }
588                    match idx_by_path_stage.entry((stage, path)) {
589                        hash_map::Entry::Occupied(map_entry) => {
590                            // This can happen due to the way the algorithm works.
591                            // The same happens in Git, but it stores the index-related data as part of its deduplicating tree.
592                            // We store each conflict we encounter, which also may duplicate their index entries, sometimes, but
593                            // with different values. The most recent value wins.
594                            // Instead of trying to deduplicate the index entries when the merge runs, we put the cost
595                            // to the tree-assembly - there is no way around it.
596                            let index_entry = &mut index.entries_mut()[*map_entry.get()];
597                            index_entry.mode = entry.mode.into();
598                            index_entry.id = entry.id;
599                        }
600                        hash_map::Entry::Vacant(map_entry) => {
601                            map_entry.insert(index.entries().len());
602                            index.dangerously_push_entry(
603                                Default::default(),
604                                entry.id,
605                                stage.into(),
606                                entry.mode.into(),
607                                path,
608                            );
609                        }
610                    }
611                }
612            }
613
614            let res = index.entries().len() != len;
615            match removal_mode {
616                RemovalMode::Mark => {}
617                RemovalMode::Prune => {
618                    index.remove_entries(|_, _, e| e.flags.contains(gix_index::entry::Flags::REMOVE));
619                }
620            }
621            index.sort_entries();
622            res
623        }
624    }
625}
626pub use apply_index_entries::function::apply_index_entries;