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;