@@ -39,40 +39,47 @@ pub struct Outcome<'a> {
39
39
/// Use [`has_unresolved_conflicts()`](Outcome::has_unresolved_conflicts()) to see if any action is needed
40
40
/// before using [`tree`](Outcome::tree).
41
41
pub conflicts : Vec < Conflict > ,
42
- /// `true` if `conflicts` contains only a single *unresolved* conflict in the last slot, but possibly more resolved ones.
42
+ /// `true` if `conflicts` contains only a single [*unresolved* conflict](ResolutionFailure) in the last slot, but
43
+ /// possibly more [resolved ones](Resolution) before that.
43
44
/// This also makes this outcome a very partial merge that cannot be completed.
44
45
/// Only set if [`fail_on_conflict`](Options::fail_on_conflict) is `true`.
45
46
pub failed_on_first_unresolved_conflict : bool ,
46
47
}
47
48
48
49
/// Determine what should be considered an unresolved conflict.
50
+ #[ derive( Default , Debug , Copy , Clone , Eq , PartialEq , Ord , PartialOrd , Hash ) ]
51
+ pub 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
+
49
58
///
50
- /// Note that no matter which variant, [conflicts](Conflict) with
51
- /// [resolution failure](`ResolutionFailure`) will always be unresolved.
52
- ///
53
- /// Also, when one side was modified but the other side renamed it, this will not
54
- /// be considered a conflict, even if a non-conflicting merge happened.
55
- #[ derive( Debug , Copy , Clone , Eq , PartialEq , Ord , PartialOrd , Hash ) ]
56
- pub enum TreatAsUnresolved {
57
- /// Only consider content merges with conflict markers as unresolved.
58
- ///
59
- /// Auto-resolved tree conflicts will *not* be considered unresolved.
60
- ConflictMarkers ,
61
- /// Consider content merges with conflict markers as unresolved, and content
62
- /// merges where conflicts where auto-resolved in any way, like choosing
63
- /// *ours*, *theirs* or by their *union*.
64
- ///
65
- /// Auto-resolved tree conflicts will *not* be considered unresolved.
66
- ConflictMarkersAndAutoResolved ,
67
- /// Whenever there were conflicting renames, or conflict markers, it is unresolved.
68
- /// Note that auto-resolved content merges will *not* be considered unresolved.
69
- ///
70
- /// Also note that files that were changed in one and renamed in another will
71
- /// be moved into place, which will be considered resolved.
72
- Renames ,
73
- /// Similar to [`Self::Renames`], but auto-resolved content-merges will
74
- /// also be considered unresolved.
75
- RenamesAndAutoResolvedContent ,
59
+ pub mod treat_as_unresolved {
60
+ /// Which kind of content merges should be considered unresolved?
61
+ #[ derive( Default , Debug , Copy , Clone , Eq , PartialEq , Ord , PartialOrd , Hash ) ]
62
+ pub enum ContentMerge {
63
+ /// Content merges that still show conflict markers.
64
+ #[ default]
65
+ Markers ,
66
+ /// Content merges who would have conflicted if it wasn't for a
67
+ /// [resolution strategy](crate::blob::builtin_driver::text::Conflict::ResolveWithOurs).
68
+ ForcedResolution ,
69
+ }
70
+
71
+ /// Which kind of tree merges should be considered unresolved?
72
+ #[ derive( Default , Debug , Copy , Clone , Eq , PartialEq , Ord , PartialOrd , Hash ) ]
73
+ pub enum TreeMerge {
74
+ /// All failed renames.
75
+ Undecidable ,
76
+ /// All failed renames, and the ones where a tree item was renamed to avoid a clash.
77
+ #[ default]
78
+ EvasiveRenames ,
79
+ /// All of `EvasiveRenames`, and tree merges that would have conflicted but which were resolved
80
+ /// with a [resolution strategy](super::ResolveWith).
81
+ ForcedResolution ,
82
+ }
76
83
}
77
84
78
85
impl Outcome < ' _ > {
@@ -174,35 +181,36 @@ impl Conflict {
174
181
/// Return `true` if this instance is considered unresolved based on the criterion specified by `how`.
175
182
pub fn is_unresolved ( & self , how : TreatAsUnresolved ) -> bool {
176
183
use crate :: blob;
177
- let content_merge_matches = |info : & ContentMerge | match how {
178
- TreatAsUnresolved :: ConflictMarkers | TreatAsUnresolved :: Renames => {
179
- matches ! ( info. resolution, blob:: Resolution :: Conflict )
180
- }
181
- TreatAsUnresolved :: RenamesAndAutoResolvedContent | TreatAsUnresolved :: ConflictMarkersAndAutoResolved => {
184
+ let content_merge_matches = |info : & ContentMerge | match how. content_merge {
185
+ treat_as_unresolved:: ContentMerge :: Markers => matches ! ( info. resolution, blob:: Resolution :: Conflict ) ,
186
+ treat_as_unresolved:: ContentMerge :: ForcedResolution => {
182
187
matches ! (
183
188
info. resolution,
184
189
blob:: Resolution :: Conflict | blob:: Resolution :: CompleteWithAutoResolvedConflict
185
190
)
186
191
}
187
192
} ;
188
- match how {
189
- TreatAsUnresolved :: ConflictMarkers | TreatAsUnresolved :: ConflictMarkersAndAutoResolved => {
193
+ match how. tree_merge {
194
+ treat_as_unresolved :: TreeMerge :: Undecidable => {
190
195
self . resolution . is_err ( ) || self . content_merge ( ) . map_or ( false , |info| content_merge_matches ( & info) )
191
196
}
192
- TreatAsUnresolved :: Renames | TreatAsUnresolved :: RenamesAndAutoResolvedContent => match & self . resolution {
193
- Ok ( success) => match success {
194
- Resolution :: SourceLocationAffectedByRename { .. } => false ,
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) ,
200
- Resolution :: OursModifiedTheirsModifiedThenBlobContentMerge { merged_blob } => {
201
- content_merge_matches ( merged_blob)
202
- }
203
- } ,
204
- Err ( _failure) => true ,
205
- } ,
197
+ treat_as_unresolved:: TreeMerge :: EvasiveRenames | treat_as_unresolved:: TreeMerge :: ForcedResolution => {
198
+ match & self . resolution {
199
+ Ok ( success) => match success {
200
+ Resolution :: SourceLocationAffectedByRename { .. } => false ,
201
+ Resolution :: Forced ( _) => how. tree_merge == treat_as_unresolved:: TreeMerge :: ForcedResolution ,
202
+ Resolution :: OursModifiedTheirsRenamedAndChangedThenRename {
203
+ merged_blob,
204
+ final_location,
205
+ ..
206
+ } => final_location. is_some ( ) || merged_blob. as_ref ( ) . map_or ( false , content_merge_matches) ,
207
+ Resolution :: OursModifiedTheirsModifiedThenBlobContentMerge { merged_blob } => {
208
+ content_merge_matches ( merged_blob)
209
+ }
210
+ } ,
211
+ Err ( _failure) => true ,
212
+ }
213
+ }
206
214
}
207
215
}
208
216
@@ -237,13 +245,8 @@ impl Conflict {
237
245
238
246
/// Return information about the content merge if it was performed.
239
247
pub fn content_merge ( & self ) -> Option < ContentMerge > {
240
- match & self . resolution {
241
- Ok ( success) => match success {
242
- Resolution :: SourceLocationAffectedByRename { .. } => None ,
243
- Resolution :: OursModifiedTheirsRenamedAndChangedThenRename { merged_blob, .. } => * merged_blob,
244
- Resolution :: OursModifiedTheirsModifiedThenBlobContentMerge { merged_blob } => Some ( * merged_blob) ,
245
- } ,
246
- Err ( failure) => match failure {
248
+ fn failure_merged_blob ( failure : & ResolutionFailure ) -> Option < ContentMerge > {
249
+ match failure {
247
250
ResolutionFailure :: OursRenamedTheirsRenamedDifferently { merged_blob } => * merged_blob,
248
251
ResolutionFailure :: Unknown
249
252
| ResolutionFailure :: OursModifiedTheirsDeleted
@@ -253,7 +256,16 @@ impl Conflict {
253
256
}
254
257
| ResolutionFailure :: OursAddedTheirsAddedTypeMismatch { .. }
255
258
| ResolutionFailure :: OursDeletedTheirsRenamed => None ,
259
+ }
260
+ }
261
+ match & self . resolution {
262
+ Ok ( success) => match success {
263
+ Resolution :: Forced ( failure) => failure_merged_blob ( failure) ,
264
+ Resolution :: SourceLocationAffectedByRename { .. } => None ,
265
+ Resolution :: OursModifiedTheirsRenamedAndChangedThenRename { merged_blob, .. } => * merged_blob,
266
+ Resolution :: OursModifiedTheirsModifiedThenBlobContentMerge { merged_blob } => Some ( * merged_blob) ,
256
267
} ,
268
+ Err ( failure) => failure_merged_blob ( failure) ,
257
269
}
258
270
}
259
271
}
@@ -291,6 +303,9 @@ pub enum Resolution {
291
303
/// The outcome of the content merge.
292
304
merged_blob : ContentMerge ,
293
305
} ,
306
+ /// This is a resolution failure was forcefully turned into a usable resolution, i.e. [making a choice](ResolveWith)
307
+ /// is turned into a valid resolution.
308
+ Forced ( ResolutionFailure ) ,
294
309
}
295
310
296
311
/// Describes of a conflict involving *our* change and *their* failed to be resolved.
@@ -358,13 +373,26 @@ pub struct Options {
358
373
/// If `None`, when symlinks clash *ours* will be chosen and a conflict will occur.
359
374
/// Otherwise, the same logic applies as for the merge of binary resources.
360
375
pub symlink_conflicts : Option < crate :: blob:: builtin_driver:: binary:: ResolveWith > ,
361
- /// If `true`, instead of issuing a conflict with [`ResolutionFailure`], do nothing and keep the base/ancestor
362
- /// version. This is useful when one wants to avoid any kind of merge-conflict to have *some*, *lossy* resolution.
363
- pub allow_lossy_resolution : bool ,
376
+ /// If `None`, tree irreconcilable tree conflicts will result in [resolution failures](ResolutionFailure).
377
+ /// Otherwise, one can choose a side. Note that it's still possible to determine that auto-resolution happened
378
+ /// despite this choice, which allows carry forward the conflicting information, possibly for later resolution.
379
+ /// If `Some(…)`, irreconcilable conflicts are reconciled by making a choice. This mlso means that [`Conflict::entries()`]
380
+ /// won't be set as the conflict was officially resolved.
381
+ pub tree_conflicts : Option < ResolveWith > ,
382
+ }
383
+
384
+ /// Decide how to resolve tree-related conflicts, but only those that have [no way of being correct](ResolutionFailure).
385
+ #[ derive( Copy , Clone , Debug , Eq , PartialEq , Ord , PartialOrd , Hash ) ]
386
+ pub enum ResolveWith {
387
+ /// On irreconcilable conflict, choose neither *our* nor *their* state, but keep the common *ancestor* state instead.
388
+ Ancestor ,
389
+ /// On irreconcilable conflict, choose *our* side
390
+ Ours ,
364
391
}
365
392
366
393
pub ( super ) mod function;
367
394
mod utils;
395
+ ///
368
396
pub mod apply_index_entries {
369
397
370
398
pub ( super ) mod function {
@@ -398,6 +426,7 @@ pub mod apply_index_entries {
398
426
for conflict in conflicts. iter ( ) . filter ( |c| c. is_unresolved ( how) ) {
399
427
let ( renamed_path, current_path) : ( Option < & BStr > , & BStr ) = match & conflict. resolution {
400
428
Ok ( success) => match success {
429
+ Resolution :: Forced ( _) => continue ,
401
430
Resolution :: SourceLocationAffectedByRename { final_location } => {
402
431
( Some ( final_location. as_bstr ( ) ) , final_location. as_bstr ( ) )
403
432
}
0 commit comments