@@ -81,6 +81,21 @@ impl Outcome<'_> {
8181 pub fn has_unresolved_conflicts ( & self , how : TreatAsUnresolved ) -> bool {
8282 self . conflicts . iter ( ) . any ( |c| c. is_unresolved ( how) )
8383 }
84+
85+ /// Returns `true` if `index` changed as we applied conflicting stages to it, using `how` to determine if a
86+ /// conflict should be considered unresolved.
87+ /// It's important that `index` is at the state of [`Self::tree`].
88+ ///
89+ /// Note that in practice, whenever there is a single [conflict], this function will return `true`.
90+ /// Also, the unconflicted stage of such entries will be removed merely by setting a flag, so the
91+ /// in-memory entry is still present.
92+ pub fn index_changed_after_applying_conflicts (
93+ & self ,
94+ index : & mut gix_index:: State ,
95+ how : TreatAsUnresolved ,
96+ ) -> Result < bool , apply_index_entries:: Error > {
97+ apply_index_entries ( & self . conflicts , how, index)
98+ }
8499}
85100
86101/// A description of a conflict (i.e. merge issue without an auto-resolution) as seen during a [tree-merge](crate::tree()).
@@ -99,11 +114,30 @@ pub struct Conflict {
99114 pub ours : Change ,
100115 /// The change representing *their* side.
101116 pub theirs : Change ,
117+ /// An array to store an entry for each stage of the conflict.
118+ ///
119+ /// * `entries[0]` => Base
120+ /// * `entries[1]` => Ours
121+ /// * `entries[2]` => Theirs
122+ ///
123+ /// Note that ours and theirs might be swapped, so one should access it through [`Self::entries()`] to compensate for that.
124+ pub entries : [ Option < ConflictIndexEntry > ; 3 ] ,
102125 /// Determine how to interpret the `ours` and `theirs` fields. This is used to implement [`Self::changes_in_resolution()`]
103126 /// and [`Self::into_parts_by_resolution()`].
104127 map : ConflictMapping ,
105128}
106129
130+ /// A conflicting entry for insertion into the index.
131+ /// It will always be either on stage 1 (ancestor/base), 2 (ours) or 3 (theirs)
132+ #[ derive( Debug , Clone , Copy ) ]
133+ pub struct ConflictIndexEntry {
134+ /// The kind of object at this stage.
135+ /// Note that it's possible that this is a directory, for instance if a directory was replaced with a file.
136+ pub mode : gix_object:: tree:: EntryMode ,
137+ /// The id defining the state of the object.
138+ pub id : gix_hash:: ObjectId ,
139+ }
140+
107141/// A utility to help define which side is what in the [`Conflict`] type.
108142#[ derive( Debug , Clone , Copy ) ]
109143enum ConflictMapping {
@@ -178,6 +212,14 @@ impl Conflict {
178212 }
179213 }
180214
215+ /// Return the index entries for insertion into the index, to match with what's returned by [`Self::changes_in_resolution()`].
216+ pub fn entries ( & self ) -> [ Option < ConflictIndexEntry > ; 3 ] {
217+ match self . map {
218+ ConflictMapping :: Original => self . entries ,
219+ ConflictMapping :: Swapped => [ self . entries [ 0 ] , self . entries [ 2 ] , self . entries [ 1 ] ] ,
220+ }
221+ }
222+
181223 /// Return information about the content merge if it was performed.
182224 pub fn content_merge ( & self ) -> Option < ContentMerge > {
183225 match & self . resolution {
@@ -308,3 +350,96 @@ pub struct Options {
308350
309351pub ( super ) mod function;
310352mod utils;
353+ pub mod apply_index_entries {
354+
355+ pub ( super ) mod function {
356+ use crate :: tree:: { Conflict , Resolution , ResolutionFailure , TreatAsUnresolved } ;
357+ use bstr:: { BString , ByteSlice } ;
358+
359+ /// The error returned by [`apply_index_entries()`].
360+ #[ derive( Debug , thiserror:: Error ) ]
361+ #[ allow( missing_docs) ]
362+ pub enum Error {
363+ #[ error(
364+ "Could not find '{path}' in the index, even though it should have the non-conflicting variant of it."
365+ ) ]
366+ MissingPathInIndex { path : BString } ,
367+ }
368+
369+ /// Returns `true` if `index` changed as we applied conflicting stages to it, using `how` to determine if a
370+ /// conflict should be considered unresolved.
371+ /// Once a stage of a path conflicts, the unconflicting stage is removed even though it might be the one
372+ /// that is currently checked out.
373+ /// This removal, however, is only done by flagging it with [gix_index::entry::Flags::REMOVE], which means
374+ /// these entries won't be written back to disk but will still be present in the index.
375+ /// It's important that `index` matches the tree that was produced as part of the merge that also
376+ /// brought about `conflicts`, or else this function will fail if it cannot find the path matching
377+ /// the conflicting entries.
378+ ///
379+ /// Note that in practice, whenever there is a single [conflict], this function will return `true`.
380+ /// Errors can only occour if `index` isn't the one created from the merged tree that produced the `conflicts`.
381+ pub fn apply_index_entries (
382+ conflicts : & [ Conflict ] ,
383+ how : TreatAsUnresolved ,
384+ index : & mut gix_index:: State ,
385+ ) -> Result < bool , Error > {
386+ let len = index. entries ( ) . len ( ) ;
387+ for conflict in conflicts. iter ( ) . filter ( |c| c. is_unresolved ( how) ) {
388+ let path = match & conflict. resolution {
389+ Ok ( success) => match success {
390+ Resolution :: SourceLocationAffectedByRename { final_location } => Some ( final_location) ,
391+ Resolution :: OursModifiedTheirsRenamedAndChangedThenRename { final_location, .. } => {
392+ final_location. as_ref ( )
393+ }
394+ Resolution :: OursModifiedTheirsModifiedThenBlobContentMerge { .. } => None ,
395+ } ,
396+ Err ( failure) => match failure {
397+ ResolutionFailure :: OursRenamedTheirsRenamedDifferently { .. }
398+ | ResolutionFailure :: OursModifiedTheirsRenamedTypeMismatch
399+ | ResolutionFailure :: OursDeletedTheirsRenamed
400+ | ResolutionFailure :: OursModifiedTheirsDeleted
401+ | ResolutionFailure :: Unknown => None ,
402+ ResolutionFailure :: OursModifiedTheirsDirectoryThenOursRenamed {
403+ renamed_unique_path_to_modified_blob,
404+ } => Some ( renamed_unique_path_to_modified_blob) ,
405+ ResolutionFailure :: OursAddedTheirsAddedTypeMismatch { their_unique_location } => {
406+ Some ( their_unique_location)
407+ }
408+ } ,
409+ } ;
410+ let path = path. map_or_else ( || conflict. ours . location ( ) , |path| path. as_bstr ( ) ) ;
411+ if conflict. entries . iter ( ) . any ( |e| e. is_some ( ) ) {
412+ let existing_entry = {
413+ let pos = index
414+ . entry_index_by_path_and_stage_bounded ( path, gix_index:: entry:: Stage :: Unconflicted , len)
415+ . ok_or_else ( || Error :: MissingPathInIndex { path : path. to_owned ( ) } ) ?;
416+ & mut index. entries_mut ( ) [ pos]
417+ } ;
418+ existing_entry. flags . insert ( gix_index:: entry:: Flags :: REMOVE ) ;
419+ }
420+
421+ let entries_with_stage = conflict. entries ( ) . into_iter ( ) . enumerate ( ) . filter_map ( |( idx, entry) | {
422+ entry. map ( |e| {
423+ (
424+ match idx {
425+ 0 => gix_index:: entry:: Stage :: Base ,
426+ 1 => gix_index:: entry:: Stage :: Ours ,
427+ 2 => gix_index:: entry:: Stage :: Theirs ,
428+ _ => unreachable ! ( "fixed size arra with three items" ) ,
429+ } ,
430+ e,
431+ )
432+ } )
433+ } ) ;
434+ for ( stage, entry) in entries_with_stage {
435+ index. dangerously_push_entry ( Default :: default ( ) , entry. id , stage. into ( ) , entry. mode . into ( ) , path) ;
436+ }
437+ }
438+
439+ index. sort_entries ( ) ;
440+ Ok ( index. entries ( ) . len ( ) != len)
441+ }
442+ }
443+ pub use function:: Error ;
444+ }
445+ pub use apply_index_entries:: function:: apply_index_entries;
0 commit comments