@@ -81,6 +81,17 @@ impl Outcome<'_> {
81
81
pub fn has_unresolved_conflicts ( & self , how : TreatAsUnresolved ) -> bool {
82
82
self . conflicts . iter ( ) . any ( |c| c. is_unresolved ( how) )
83
83
}
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](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 ( & self , index : & mut gix_index:: State , how : TreatAsUnresolved ) -> bool {
93
+ apply_index_entries ( & self . conflicts , how, index)
94
+ }
84
95
}
85
96
86
97
/// A description of a conflict (i.e. merge issue without an auto-resolution) as seen during a [tree-merge](crate::tree()).
@@ -99,11 +110,45 @@ pub struct Conflict {
99
110
pub ours : Change ,
100
111
/// The change representing *their* side.
101
112
pub theirs : Change ,
113
+ /// An array to store an entry for each stage of the conflict.
114
+ ///
115
+ /// * `entries[0]` => Base
116
+ /// * `entries[1]` => Ours
117
+ /// * `entries[2]` => Theirs
118
+ ///
119
+ /// Note that ours and theirs might be swapped, so one should access it through [`Self::entries()`] to compensate for that.
120
+ pub entries : [ Option < ConflictIndexEntry > ; 3 ] ,
102
121
/// Determine how to interpret the `ours` and `theirs` fields. This is used to implement [`Self::changes_in_resolution()`]
103
122
/// and [`Self::into_parts_by_resolution()`].
104
123
map : ConflictMapping ,
105
124
}
106
125
126
+ /// A conflicting entry for insertion into the index.
127
+ /// It will always be either on stage 1 (ancestor/base), 2 (ours) or 3 (theirs)
128
+ #[ derive( Debug , Clone , Copy ) ]
129
+ pub struct ConflictIndexEntry {
130
+ /// The kind of object at this stage.
131
+ /// Note that it's possible that this is a directory, for instance if a directory was replaced with a file.
132
+ pub mode : gix_object:: tree:: EntryMode ,
133
+ /// The id defining the state of the object.
134
+ pub id : gix_hash:: ObjectId ,
135
+ /// Hidden, maybe one day we can do without?
136
+ path_hint : Option < ConflictIndexEntryPathHint > ,
137
+ }
138
+
139
+ /// A hint for [`apply_index_entries()`] to know which paths to use for an entry.
140
+ /// This is only used when necessary.
141
+ #[ derive( Debug , Clone , Copy ) ]
142
+ enum ConflictIndexEntryPathHint {
143
+ /// Use the previous path, i.e. rename source.
144
+ Source ,
145
+ /// Use the current path as it is in the tree.
146
+ Current ,
147
+ /// Use the path of the final destination, or *their* name.
148
+ /// It's definitely finicky, as we don't store the actual path and instead refer to it.
149
+ RenamedOrTheirs ,
150
+ }
151
+
107
152
/// A utility to help define which side is what in the [`Conflict`] type.
108
153
#[ derive( Debug , Clone , Copy ) ]
109
154
enum ConflictMapping {
@@ -147,7 +192,11 @@ impl Conflict {
147
192
TreatAsUnresolved :: Renames | TreatAsUnresolved :: RenamesAndAutoResolvedContent => match & self . resolution {
148
193
Ok ( success) => match success {
149
194
Resolution :: SourceLocationAffectedByRename { .. } => false ,
150
- Resolution :: OursModifiedTheirsRenamedAndChangedThenRename { .. } => true ,
195
+ Resolution :: OursModifiedTheirsRenamedAndChangedThenRename {
196
+ merged_blob,
197
+ final_location,
198
+ ..
199
+ } => final_location. is_some ( ) || merged_blob. as_ref ( ) . map_or ( false , content_merge_matches) ,
151
200
Resolution :: OursModifiedTheirsModifiedThenBlobContentMerge { merged_blob } => {
152
201
content_merge_matches ( merged_blob)
153
202
}
@@ -178,6 +227,14 @@ impl Conflict {
178
227
}
179
228
}
180
229
230
+ /// Return the index entries for insertion into the index, to match with what's returned by [`Self::changes_in_resolution()`].
231
+ pub fn entries ( & self ) -> [ Option < ConflictIndexEntry > ; 3 ] {
232
+ match self . map {
233
+ ConflictMapping :: Original => self . entries ,
234
+ ConflictMapping :: Swapped => [ self . entries [ 0 ] , self . entries [ 2 ] , self . entries [ 1 ] ] ,
235
+ }
236
+ }
237
+
181
238
/// Return information about the content merge if it was performed.
182
239
pub fn content_merge ( & self ) -> Option < ContentMerge > {
183
240
match & self . resolution {
@@ -308,3 +365,132 @@ pub struct Options {
308
365
309
366
pub ( super ) mod function;
310
367
mod utils;
368
+ pub mod apply_index_entries {
369
+
370
+ pub ( super ) mod function {
371
+ use crate :: tree:: { Conflict , ConflictIndexEntryPathHint , Resolution , ResolutionFailure , TreatAsUnresolved } ;
372
+ use bstr:: { BStr , ByteSlice } ;
373
+ use std:: collections:: { hash_map, HashMap } ;
374
+
375
+ /// Returns `true` if `index` changed as we applied conflicting stages to it, using `how` to determine if a
376
+ /// conflict should be considered unresolved.
377
+ /// Once a stage of a path conflicts, the unconflicting stage is removed even though it might be the one
378
+ /// that is currently checked out.
379
+ /// This removal, however, is only done by flagging it with [gix_index::entry::Flags::REMOVE], which means
380
+ /// these entries won't be written back to disk but will still be present in the index.
381
+ /// It's important that `index` matches the tree that was produced as part of the merge that also
382
+ /// brought about `conflicts`, or else this function will fail if it cannot find the path matching
383
+ /// the conflicting entries.
384
+ ///
385
+ /// Note that in practice, whenever there is a single [conflict](Conflict), this function will return `true`.
386
+ /// Errors can only occour if `index` isn't the one created from the merged tree that produced the `conflicts`.
387
+ pub fn apply_index_entries (
388
+ conflicts : & [ Conflict ] ,
389
+ how : TreatAsUnresolved ,
390
+ index : & mut gix_index:: State ,
391
+ ) -> bool {
392
+ let len = index. entries ( ) . len ( ) ;
393
+ let mut idx_by_path_stage = HashMap :: < ( gix_index:: entry:: Stage , & BStr ) , usize > :: default ( ) ;
394
+ for conflict in conflicts. iter ( ) . filter ( |c| c. is_unresolved ( how) ) {
395
+ let ( renamed_path, current_path) : ( Option < & BStr > , & BStr ) = match & conflict. resolution {
396
+ Ok ( success) => match success {
397
+ Resolution :: SourceLocationAffectedByRename { final_location } => {
398
+ ( Some ( final_location. as_bstr ( ) ) , final_location. as_bstr ( ) )
399
+ }
400
+ Resolution :: OursModifiedTheirsRenamedAndChangedThenRename { final_location, .. } => (
401
+ final_location. as_ref ( ) . map ( |p| p. as_bstr ( ) ) ,
402
+ conflict. changes_in_resolution ( ) . 1 . location ( ) ,
403
+ ) ,
404
+ Resolution :: OursModifiedTheirsModifiedThenBlobContentMerge { .. } => {
405
+ ( None , conflict. ours . location ( ) )
406
+ }
407
+ } ,
408
+ Err ( failure) => match failure {
409
+ ResolutionFailure :: OursRenamedTheirsRenamedDifferently { .. } => {
410
+ ( Some ( conflict. theirs . location ( ) ) , conflict. ours . location ( ) )
411
+ }
412
+ ResolutionFailure :: OursModifiedTheirsRenamedTypeMismatch
413
+ | ResolutionFailure :: OursDeletedTheirsRenamed
414
+ | ResolutionFailure :: OursModifiedTheirsDeleted
415
+ | ResolutionFailure :: Unknown => ( None , conflict. ours . location ( ) ) ,
416
+ ResolutionFailure :: OursModifiedTheirsDirectoryThenOursRenamed {
417
+ renamed_unique_path_to_modified_blob,
418
+ } => (
419
+ Some ( renamed_unique_path_to_modified_blob. as_bstr ( ) ) ,
420
+ conflict. ours . location ( ) ,
421
+ ) ,
422
+ ResolutionFailure :: OursAddedTheirsAddedTypeMismatch { their_unique_location } => {
423
+ ( Some ( their_unique_location. as_bstr ( ) ) , conflict. ours . location ( ) )
424
+ }
425
+ } ,
426
+ } ;
427
+ let source_path = conflict. ours . source_location ( ) ;
428
+
429
+ let entries_with_stage = conflict. entries ( ) . into_iter ( ) . enumerate ( ) . filter_map ( |( idx, entry) | {
430
+ entry. filter ( |e| e. mode . is_no_tree ( ) ) . map ( |e| {
431
+ (
432
+ match idx {
433
+ 0 => gix_index:: entry:: Stage :: Base ,
434
+ 1 => gix_index:: entry:: Stage :: Ours ,
435
+ 2 => gix_index:: entry:: Stage :: Theirs ,
436
+ _ => unreachable ! ( "fixed size array with three items" ) ,
437
+ } ,
438
+ match e. path_hint {
439
+ None => renamed_path. unwrap_or ( current_path) ,
440
+ Some ( ConflictIndexEntryPathHint :: Source ) => source_path,
441
+ Some ( ConflictIndexEntryPathHint :: Current ) => current_path,
442
+ Some ( ConflictIndexEntryPathHint :: RenamedOrTheirs ) => {
443
+ renamed_path. unwrap_or_else ( || conflict. changes_in_resolution ( ) . 1 . location ( ) )
444
+ }
445
+ } ,
446
+ e,
447
+ )
448
+ } )
449
+ } ) ;
450
+
451
+ if !entries_with_stage. clone ( ) . any ( |( _, path, _) | {
452
+ index
453
+ . entry_index_by_path_and_stage_bounded ( path, gix_index:: entry:: Stage :: Unconflicted , len)
454
+ . is_some ( )
455
+ } ) {
456
+ continue ;
457
+ }
458
+
459
+ for ( stage, path, entry) in entries_with_stage {
460
+ if let Some ( pos) =
461
+ index. entry_index_by_path_and_stage_bounded ( path, gix_index:: entry:: Stage :: Unconflicted , len)
462
+ {
463
+ index. entries_mut ( ) [ pos] . flags . insert ( gix_index:: entry:: Flags :: REMOVE ) ;
464
+ } ;
465
+ match idx_by_path_stage. entry ( ( stage, path) ) {
466
+ hash_map:: Entry :: Occupied ( map_entry) => {
467
+ // This can happen due to the way the algorithm works.
468
+ // The same happens in Git, but it stores the index-related data as part of its deduplicating tree.
469
+ // We store each conflict we encounter, which also may duplicate their index entries, sometimes, but
470
+ // with different values. The most recent value wins.
471
+ // Instead of trying to deduplicate the index entries when the merge runs, we put the cost
472
+ // to the tree-assembly - there is no way around it.
473
+ let index_entry = & mut index. entries_mut ( ) [ * map_entry. get ( ) ] ;
474
+ index_entry. mode = entry. mode . into ( ) ;
475
+ index_entry. id = entry. id ;
476
+ }
477
+ hash_map:: Entry :: Vacant ( map_entry) => {
478
+ map_entry. insert ( index. entries ( ) . len ( ) ) ;
479
+ index. dangerously_push_entry (
480
+ Default :: default ( ) ,
481
+ entry. id ,
482
+ stage. into ( ) ,
483
+ entry. mode . into ( ) ,
484
+ path,
485
+ ) ;
486
+ }
487
+ } ;
488
+ }
489
+ }
490
+
491
+ index. sort_entries ( ) ;
492
+ index. entries ( ) . len ( ) != len
493
+ }
494
+ }
495
+ }
496
+ pub use apply_index_entries:: function:: apply_index_entries;
0 commit comments