@@ -43,31 +43,25 @@ pub struct Outcome<'a> {
4343 pub failed_on_first_unresolved_conflict : bool ,
4444}
4545
46+ /// Determine what should be considered an unresolved conflict.
47+ ///
48+ /// Note that no matter which variant, [conflicts](Conflict) with [resolution failure](`ResolutionFailure`)
49+ /// will always be unresolved.
50+ ///
51+ /// However, those whose resolution was
52+ #[ derive( Debug , Copy , Clone , Eq , PartialEq , Ord , PartialOrd , Hash ) ]
53+ pub enum UnresolvedConflict {
54+ /// Only consider content merges with conflict markers as unresolved.
55+ ConflictMarkers ,
56+ /// Whenever there was any rename, or conflict markers, it is unresolved.
57+ Renames ,
58+ }
59+
4660impl Outcome < ' _ > {
4761 /// Return `true` if there is any conflict that would still need to be resolved as they would yield undesirable trees.
48- /// Note that this interpretation of conflicts and their resolution, see
49- /// [`has_unresolved_conflicts_strict`](Self::has_unresolved_conflicts_strict).
50- pub fn has_unresolved_conflicts ( & self ) -> bool {
51- self . conflicts . iter ( ) . any ( |c| {
52- c. resolution . is_err ( )
53- || c. content_merge ( ) . map_or ( false , |info| {
54- matches ! ( info. resolution, crate :: blob:: Resolution :: Conflict )
55- } )
56- } )
57- }
58-
59- /// Return `true` only if there was any (even resolved) conflict in the tree structure, or if there are still conflict markers.
60- pub fn has_unresolved_conflicts_strict ( & self ) -> bool {
61- self . conflicts . iter ( ) . any ( |c| match & c. resolution {
62- Ok ( success) => match success {
63- Resolution :: OursAddedTheirsAddedTypeMismatch { .. }
64- | Resolution :: OursModifiedTheirsRenamedAndChangedThenRename { .. } => true ,
65- Resolution :: OursModifiedTheirsModifiedThenBlobContentMerge { merged_blob } => {
66- matches ! ( merged_blob. resolution, crate :: blob:: Resolution :: Conflict )
67- }
68- } ,
69- Err ( _failure) => true ,
70- } )
62+ /// This is based on `how` to determine what should be considered unresolved.
63+ pub fn has_unresolved_conflicts ( & self , how : UnresolvedConflict ) -> bool {
64+ function:: is_unresolved ( & self . conflicts , how)
7165 }
7266}
7367
@@ -135,14 +129,19 @@ impl Conflict {
135129 pub fn content_merge ( & self ) -> Option < ContentMerge > {
136130 match & self . resolution {
137131 Ok ( success) => match success {
138- Resolution :: OursAddedTheirsAddedTypeMismatch { .. } => None ,
132+ Resolution :: SourceLocationAffectedByRename { .. } => None ,
139133 Resolution :: OursModifiedTheirsRenamedAndChangedThenRename { merged_blob, .. } => * merged_blob,
140134 Resolution :: OursModifiedTheirsModifiedThenBlobContentMerge { merged_blob } => Some ( * merged_blob) ,
141135 } ,
142136 Err ( failure) => match failure {
143- ResolutionFailure :: OursModifiedTheirsDirectoryThenOursRenamed {
144- renamed_path_to_modified_blob : _,
137+ ResolutionFailure :: OursRenamedTheirsRenamedDifferently { merged_blob } => * merged_blob,
138+ ResolutionFailure :: Unknown
139+ | ResolutionFailure :: OursModifiedTheirsDeleted
140+ | ResolutionFailure :: OursModifiedTheirsRenamedTypeMismatch
141+ | ResolutionFailure :: OursModifiedTheirsDirectoryThenOursRenamed {
142+ renamed_unique_path_to_modified_blob : _,
145143 }
144+ | ResolutionFailure :: OursAddedTheirsAddedTypeMismatch { .. }
146145 | ResolutionFailure :: OursDeletedTheirsRenamed => None ,
147146 } ,
148147 }
@@ -154,11 +153,20 @@ impl Conflict {
154153/// Note that all resolutions are side-agnostic, so *ours* could also have been *theirs* and vice versa.
155154#[ derive( Debug , Clone ) ]
156155pub enum Resolution {
156+ /// *ours* had a renamed directory and *theirs* made a change in the now renamed directory.
157+ /// We moved that change into its location.
158+ SourceLocationAffectedByRename {
159+ /// The repository-relative path to the location that the change ended up in after
160+ /// being affected by a renamed directory.
161+ final_location : BString ,
162+ } ,
157163 /// *ours* was a modified blob and *theirs* renamed that blob.
158164 /// We moved the changed blob from *ours* to its new location, and merged it successfully.
159165 /// If this is a `copy`, the source of the copy was set to be the changed blob as well so both match.
160166 OursModifiedTheirsRenamedAndChangedThenRename {
161- /// If not `None`, the content of the involved blob had to be merged.
167+ /// If one side added the executable bit, we always add it in the merged result.
168+ merged_mode : Option < gix_object:: tree:: EntryMode > ,
169+ /// If `Some(…)`, the content of the involved blob had to be merged.
162170 merged_blob : Option < ContentMerge > ,
163171 /// The repository relative path to the location the blob finally ended up in.
164172 /// It's `Some()` only if *they* rewrote the blob into a directory which *we* renamed on *our* side.
@@ -172,11 +180,38 @@ pub enum Resolution {
172180 /// The outcome of the content merge.
173181 merged_blob : ContentMerge ,
174182 } ,
183+ }
184+
185+ /// Describes of a conflict involving *our* change and *their* failed to be resolved.
186+ #[ derive( Debug , Clone ) ]
187+ pub enum ResolutionFailure {
188+ /// *ours* was renamed, but *theirs* was renamed differently. Both versions will be present in the tree,
189+ OursRenamedTheirsRenamedDifferently {
190+ /// If `Some(…)`, the content of the involved blob had to be merged.
191+ merged_blob : Option < ContentMerge > ,
192+ } ,
193+ /// *ours* was modified, but *theirs* was turned into a directory, so *ours* was renamed to a non-conflicting path.
194+ OursModifiedTheirsDirectoryThenOursRenamed {
195+ /// The path at which `ours` can be found in the tree - it's in the same directory that it was in before.
196+ renamed_unique_path_to_modified_blob : BString ,
197+ } ,
175198 /// *ours* was added (or renamed into place) with a different mode than theirs, e.g. blob and symlink, and we kept
199+ /// the symlink in its original location, renaming the other side to `their_unique_location`.
176200 OursAddedTheirsAddedTypeMismatch {
177- /// The location at which *their* state was placed to resolve the name and type clash.
178- their_final_location : BString ,
201+ /// The location at which *their* state was placed to resolve the name and type clash, named to indicate
202+ /// where the entry is coming from.
203+ their_unique_location : BString ,
179204 } ,
205+ /// *ours* was modified, and they renamed the same file, but there is also a non-mergable type-change.
206+ /// Here we keep both versions of the file.
207+ OursModifiedTheirsRenamedTypeMismatch ,
208+ /// *ours* was deleted, but *theirs* was renamed.
209+ OursDeletedTheirsRenamed ,
210+ /// *ours* was modified and *theirs* was deleted. We keep the modified one and ignore the deletion.
211+ OursModifiedTheirsDeleted ,
212+ /// *ours* and *theirs* are in an untested state so it can't be handled yet, and is considered a conflict
213+ /// without adding our *or* their side to the resulting tree.
214+ Unknown ,
180215}
181216
182217/// Information about a blob content merge for use in a [`Resolution`].
@@ -190,18 +225,6 @@ pub struct ContentMerge {
190225 pub resolution : crate :: blob:: Resolution ,
191226}
192227
193- /// Describes of a conflict involving *our* change and *their* failed to be resolved.
194- #[ derive( Debug , Clone ) ]
195- pub enum ResolutionFailure {
196- /// *ours* was modified, but *theirs* was turned into a directory, so *ours* was renamed to a non-conflicting path.
197- OursModifiedTheirsDirectoryThenOursRenamed {
198- /// The path at which `ours` can be found in the tree - it's in the same directory that it was in before.
199- renamed_path_to_modified_blob : BString ,
200- } ,
201- /// *ours* was deleted, but *theirs* was renamed.
202- OursDeletedTheirsRenamed ,
203- }
204-
205228/// A way to configure [`tree()`](crate::tree()).
206229#[ derive( Default , Debug , Clone ) ]
207230pub struct Options {
@@ -211,14 +234,19 @@ pub struct Options {
211234 pub blob_merge : crate :: blob:: platform:: merge:: Options ,
212235 /// The context to use when invoking merge-drivers.
213236 pub blob_merge_command_ctx : gix_command:: Context ,
214- /// If `true`, the first conflict will cause the entire
215- pub fail_on_conflict : bool ,
237+ /// If `Some(what-is-unresolved)`, the first unresolved conflict will cause the entire merge to stop.
238+ /// This is useful to see if there is any conflict, without performing the whole operation.
239+ // TODO: Maybe remove this if the cost of figuring out conflicts is so low - after all, the data structures
240+ // and initial diff is the expensive thing right now, which are always done upfront.
241+ // However, this could change once we know do everything during the traversal, which probably doesn't work
242+ // without caching stuff and is too complicated to actually do.
243+ pub fail_on_conflict : Option < UnresolvedConflict > ,
216244 /// If greater than 0, each level indicates another merge-of-merge. This can be greater than
217245 /// 0 when merging merge-bases, which are merged like a pyramid.
218246 /// This value also affects the size of merge-conflict markers, to allow differentiating
219247 /// merge conflicts on each level.
220248 pub call_depth : u8 ,
221- // TODO(Perf) add a flag to allow parallelizing the tree-diff itself.
249+ // TODO(Perf) add a flag to allow parallelizing the tree-diff itself once there was some profiling .
222250}
223251
224252pub ( super ) mod function;
0 commit comments