gix_merge/tree/
mod.rs

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