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;