@@ -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,16 @@ 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 {
137+ ResolutionFailure :: OursRenamedTheirsRenamedDifferently { merged_blob } => * merged_blob,
143138 ResolutionFailure :: OursModifiedTheirsDirectoryThenOursRenamed {
144- renamed_path_to_modified_blob : _,
139+ renamed_unique_path_to_modified_blob : _,
145140 }
141+ | ResolutionFailure :: OursAddedTheirsAddedTypeMismatch { .. }
146142 | ResolutionFailure :: OursDeletedTheirsRenamed => None ,
147143 } ,
148144 }
@@ -154,11 +150,20 @@ impl Conflict {
154150/// Note that all resolutions are side-agnostic, so *ours* could also have been *theirs* and vice versa.
155151#[ derive( Debug , Clone ) ]
156152pub enum Resolution {
153+ /// *ours* had a renamed directory and *theirs* made a change in the now renamed directory.
154+ /// We moved that change into its location.
155+ SourceLocationAffectedByRename {
156+ /// The repository-relative path to the location that the change ended up in after
157+ /// being affected by a renamed directory.
158+ final_location : BString ,
159+ } ,
157160 /// *ours* was a modified blob and *theirs* renamed that blob.
158161 /// We moved the changed blob from *ours* to its new location, and merged it successfully.
159162 /// If this is a `copy`, the source of the copy was set to be the changed blob as well so both match.
160163 OursModifiedTheirsRenamedAndChangedThenRename {
161- /// If not `None`, the content of the involved blob had to be merged.
164+ /// If one side added the executable bit, we always add it in the merged result.
165+ merged_mode : Option < gix_object:: tree:: EntryMode > ,
166+ /// If `Some(…)`, the content of the involved blob had to be merged.
162167 merged_blob : Option < ContentMerge > ,
163168 /// The repository relative path to the location the blob finally ended up in.
164169 /// It's `Some()` only if *they* rewrote the blob into a directory which *we* renamed on *our* side.
@@ -172,11 +177,30 @@ pub enum Resolution {
172177 /// The outcome of the content merge.
173178 merged_blob : ContentMerge ,
174179 } ,
180+ }
181+
182+ /// Describes of a conflict involving *our* change and *their* failed to be resolved.
183+ #[ derive( Debug , Clone ) ]
184+ pub enum ResolutionFailure {
185+ /// *ours* was renamed, but *theirs* was renamed differently. Both versions will be present in the tree,
186+ OursRenamedTheirsRenamedDifferently {
187+ /// If `Some(…)`, the content of the involved blob had to be merged.
188+ merged_blob : Option < ContentMerge > ,
189+ } ,
190+ /// *ours* was modified, but *theirs* was turned into a directory, so *ours* was renamed to a non-conflicting path.
191+ OursModifiedTheirsDirectoryThenOursRenamed {
192+ /// The path at which `ours` can be found in the tree - it's in the same directory that it was in before.
193+ renamed_unique_path_to_modified_blob : BString ,
194+ } ,
175195 /// *ours* was added (or renamed into place) with a different mode than theirs, e.g. blob and symlink, and we kept
196+ /// the symlink in its original location, renaming the other side to `their_unique_location`.
176197 OursAddedTheirsAddedTypeMismatch {
177- /// The location at which *their* state was placed to resolve the name and type clash.
178- their_final_location : BString ,
198+ /// The location at which *their* state was placed to resolve the name and type clash, named to indicate
199+ /// where the entry is coming from.
200+ their_unique_location : BString ,
179201 } ,
202+ /// *ours* was deleted, but *theirs* was renamed.
203+ OursDeletedTheirsRenamed ,
180204}
181205
182206/// Information about a blob content merge for use in a [`Resolution`].
@@ -190,18 +214,6 @@ pub struct ContentMerge {
190214 pub resolution : crate :: blob:: Resolution ,
191215}
192216
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-
205217/// A way to configure [`tree()`](crate::tree()).
206218#[ derive( Default , Debug , Clone ) ]
207219pub struct Options {
@@ -211,8 +223,13 @@ pub struct Options {
211223 pub blob_merge : crate :: blob:: platform:: merge:: Options ,
212224 /// The context to use when invoking merge-drivers.
213225 pub blob_merge_command_ctx : gix_command:: Context ,
214- /// If `true`, the first conflict will cause the entire
215- pub fail_on_conflict : bool ,
226+ /// If `Some(what-is-unresolved)`, the first unresolved conflict will cause the entire merge to stop.
227+ /// This is useful to see if there is any conflict, without performing the whole operation.
228+ // TODO: Maybe remove this if the cost of figuring out conflicts is so low - after all, the data structures
229+ // and initial diff is the expensive thing right now, which are always done upfront.
230+ // However, this could change once we know do everything during the traversal, which probably doesn't work
231+ // without caching stuff and is too complicated to actually do.
232+ pub fail_on_conflict : Option < UnresolvedConflict > ,
216233 /// If greater than 0, each level indicates another merge-of-merge. This can be greater than
217234 /// 0 when merging merge-bases, which are merged like a pyramid.
218235 /// This value also affects the size of merge-conflict markers, to allow differentiating
0 commit comments