@@ -9,7 +9,9 @@ fn run_baseline() -> crate::Result {
99 root,
1010 odb,
1111 our_commit_id,
12+ our_side_name,
1213 their_commit_id,
14+ their_side_name,
1315 merge_info,
1416 case_name,
1517 } in baseline:: Expectations :: new ( & root, & cases)
@@ -25,16 +27,16 @@ fn run_baseline() -> crate::Result {
2527 percentage : Some ( 0.5 ) ,
2628 limit : 0 ,
2729 } ) ,
30+ blob_merge : Default :: default ( ) ,
2831 } ,
29- blob_merge : Default :: default ( ) ,
3032 } ;
3133 let mut actual = gix_merge:: commit (
3234 our_commit_id,
3335 their_commit_id,
3436 gix_merge:: blob:: builtin_driver:: text:: Labels {
3537 ancestor : None ,
36- current : Some ( "ours" . into ( ) ) ,
37- other : Some ( "theirs" . into ( ) ) ,
38+ current : Some ( our_side_name . as_str ( ) . into ( ) ) ,
39+ other : Some ( their_side_name . as_str ( ) . into ( ) ) ,
3840 } ,
3941 & mut graph,
4042 & mut diff_resource_cache,
@@ -43,32 +45,103 @@ fn run_baseline() -> crate::Result {
4345 options,
4446 ) ?;
4547
46- match merge_info {
47- Ok ( expected_tree_id) => {
48- let actual_id = actual. tree . write ( |tree| odb. write ( tree) ) ?;
49- assert_eq ! ( actual_id, expected_tree_id, "{case_name}: merged tree mismatch" ) ;
50- }
51- Err ( _conflicts) => {
52- todo ! ( "compare conflicts" )
53- }
48+ let actual_id = actual. tree . write ( |tree| odb. write ( tree) ) ?;
49+ assert_eq ! ( actual_id, merge_info. merged_tree, "{case_name}: merged tree mismatch" ) ;
50+ if let Some ( conflicts) = merge_info. conflicts {
51+ dbg ! ( & conflicts, & merge_info. information) ;
52+ todo ! ( "compare merge conflict information" )
5453 }
5554 }
5655
5756 Ok ( ( ) )
5857}
5958
59+ // TODO: make sure everything is read eventually, even if only to improve debug messages in case of failure.
60+ #[ allow( dead_code) ]
6061mod baseline {
62+ use gix_object:: tree:: EntryMode ;
6163 use gix_worktree:: stack:: state:: attributes;
6264 use std:: path:: { Path , PathBuf } ;
6365
64- pub struct Conflict ;
66+ /// An entry in the conflict
67+ #[ derive( Debug ) ]
68+ pub struct Entry {
69+ /// The relative path in the repository
70+ pub location : String ,
71+ /// The content id.
72+ pub id : gix_hash:: ObjectId ,
73+ /// The kind of entry.
74+ pub mode : EntryMode ,
75+ }
76+
77+ /// Keep track of all the sides of a conflict. Some might not be set to indicate removal, including the ancestor.
78+ #[ derive( Default , Debug ) ]
79+ pub struct Conflict {
80+ pub ancestor : Option < Entry > ,
81+ pub ours : Option < Entry > ,
82+ pub theirs : Option < Entry > ,
83+ }
84+
85+ #[ derive( Debug ) ]
86+ pub enum ConflictKind {
87+ /// The conflict was resolved by automatically merging the content.
88+ AutoMerging ,
89+ /// The content could not be resolved so it's conflicting.
90+ ConflictContents ,
91+ /// Directory in theirs in the way of our file
92+ ConflictDirectoryBlocksFile ,
93+ /// Modified in ours but deleted in theirs
94+ ConflictModifyDelete ,
95+ }
96+
97+ /// More loosely structured information about the `Conflict`.
98+ #[ derive( Debug ) ]
99+ pub struct ConflictInfo {
100+ /// All the paths involved in the informational message
101+ pub paths : Vec < String > ,
102+ /// The type of the conflict, further described in `message`.
103+ pub kind : ConflictKind ,
104+ /// An arbitrary message formed from paths and kind
105+ pub message : String ,
106+ }
107+
108+ impl Conflict {
109+ fn any_location ( & self ) -> Option < & str > {
110+ self . ancestor
111+ . as_ref ( )
112+ . or ( self . ours . as_ref ( ) )
113+ . or ( self . theirs . as_ref ( ) )
114+ . map ( |a| a. location . as_str ( ) )
115+ }
116+ fn storage_for ( & mut self , side : Side , location : & str ) -> Option < & mut Option < Entry > > {
117+ let current_location = self . any_location ( ) ;
118+ let location_is_same = current_location. is_none ( ) || current_location == Some ( location) ;
119+ let side = match side {
120+ Side :: Ancestor => & mut self . ancestor ,
121+ Side :: Ours => & mut self . ours ,
122+ Side :: Theirs => & mut self . theirs ,
123+ } ;
124+ ( !side. is_some ( ) && location_is_same) . then_some ( side)
125+ }
126+ }
127+
128+ pub struct MergeInfo {
129+ /// The hash of the merged tree - it may contain intermediate files if the merge didn't succeed entirely.
130+ pub merged_tree : gix_hash:: ObjectId ,
131+ /// If there were conflicts, this is the conflicting paths.
132+ pub conflicts : Option < Vec < Conflict > > ,
133+ /// Structured details which to some extent can be compared to our own conflict information.
134+ pub information : Vec < ConflictInfo > ,
135+ }
65136
66137 pub struct Expectation {
67138 pub root : PathBuf ,
68139 pub odb : gix_odb:: memory:: Proxy < gix_odb:: Handle > ,
69140 pub our_commit_id : gix_hash:: ObjectId ,
141+ pub our_side_name : String ,
70142 pub their_commit_id : gix_hash:: ObjectId ,
71- pub merge_info : Result < gix_hash:: ObjectId , Conflict > ,
143+ pub their_side_name : String ,
144+ pub merge_info : MergeInfo ,
72145 pub case_name : String ,
73146 }
74147
@@ -92,8 +165,21 @@ mod baseline {
92165 fn next ( & mut self ) -> Option < Self :: Item > {
93166 let line = self . lines . next ( ) ?;
94167 let mut tokens = line. split ( ' ' ) ;
95- let ( Some ( subdir) , Some ( our_commit_id) , Some ( their_commit_id) , Some ( merge_info_filename) ) =
96- ( tokens. next ( ) , tokens. next ( ) , tokens. next ( ) , tokens. next ( ) )
168+ let (
169+ Some ( subdir) ,
170+ Some ( our_commit_id) ,
171+ Some ( our_side_name) ,
172+ Some ( their_commit_id) ,
173+ Some ( their_side_name) ,
174+ Some ( merge_info_filename) ,
175+ ) = (
176+ tokens. next ( ) ,
177+ tokens. next ( ) ,
178+ tokens. next ( ) ,
179+ tokens. next ( ) ,
180+ tokens. next ( ) ,
181+ tokens. next ( ) ,
182+ )
97183 else {
98184 unreachable ! ( "invalid line: {line:?}" )
99185 } ;
@@ -109,7 +195,9 @@ mod baseline {
109195 root : subdir_path,
110196 odb : objects,
111197 our_commit_id,
198+ our_side_name : our_side_name. to_owned ( ) ,
112199 their_commit_id,
200+ their_side_name : their_side_name. to_owned ( ) ,
113201 merge_info,
114202 case_name : format ! (
115203 "{subdir}-{}" ,
@@ -122,11 +210,93 @@ mod baseline {
122210 }
123211 }
124212
125- fn parse_merge_info ( content : String ) -> Result < gix_hash :: ObjectId , Conflict > {
126- let mut lines = content. split ( '\0' ) . filter ( |t| !t. is_empty ( ) ) ;
213+ fn parse_merge_info ( content : String ) -> MergeInfo {
214+ let mut lines = content. split ( '\0' ) . filter ( |t| !t. is_empty ( ) ) . peekable ( ) ;
127215 let tree_id = gix_hash:: ObjectId :: from_hex ( lines. next ( ) . unwrap ( ) . as_bytes ( ) ) . unwrap ( ) ;
128- assert_eq ! ( lines. next( ) , None , "TODO: implement multi-line answer" ) ;
129- Ok ( tree_id)
216+ let mut out = MergeInfo {
217+ merged_tree : tree_id,
218+ conflicts : None ,
219+ information : Vec :: new ( ) ,
220+ } ;
221+
222+ let mut conflicts = Vec :: new ( ) ;
223+ let mut conflict = Conflict :: default ( ) ;
224+ while let Some ( line) = lines. peek ( ) {
225+ let ( entry, side) = match parse_conflict_file_info ( line) {
226+ Some ( t) => t,
227+ None => break ,
228+ } ;
229+ lines. next ( ) ;
230+ let field = match conflict. storage_for ( side, & entry. location ) {
231+ None => {
232+ conflicts. push ( conflict) ;
233+ conflict = Conflict :: default ( ) ;
234+ conflict
235+ . storage_for ( side, & entry. location )
236+ . expect ( "always available for new side" )
237+ }
238+ Some ( field) => field,
239+ } ;
240+ * field = Some ( entry) ;
241+ }
242+
243+ while lines. peek ( ) . is_some ( ) {
244+ out. information
245+ . push ( parse_info ( & mut lines) . expect ( "if there are lines, it should be valid info" ) ) ;
246+ }
247+ assert_eq ! ( lines. next( ) , None , "TODO: conflict messages" ) ;
248+ out. conflicts = ( !conflicts. is_empty ( ) ) . then_some ( conflicts) ;
249+ out
250+ }
251+
252+ #[ derive( Copy , Clone ) ]
253+ enum Side {
254+ Ancestor ,
255+ Ours ,
256+ Theirs ,
257+ }
258+
259+ fn parse_conflict_file_info ( line : & str ) -> Option < ( Entry , Side ) > {
260+ let ( info, path) = line. split_at ( line. find ( '\t' ) ?) ;
261+ let mut tokens = info. split ( ' ' ) ;
262+ let ( oct_mode, hex_id, stage) = (
263+ tokens. next ( ) . expect ( "mode" ) ,
264+ tokens. next ( ) . expect ( "id" ) ,
265+ tokens. next ( ) . expect ( "stage" ) ,
266+ ) ;
267+ assert_eq ! (
268+ tokens. next( ) ,
269+ None ,
270+ "info line not understood, expected three fields only"
271+ ) ;
272+ Some ( (
273+ Entry {
274+ location : path. to_owned ( ) ,
275+ id : gix_hash:: ObjectId :: from_hex ( hex_id. as_bytes ( ) ) . unwrap ( ) ,
276+ mode : EntryMode ( gix_utils:: btoi:: to_signed_with_radix :: < usize > ( oct_mode. as_bytes ( ) , 8 ) . unwrap ( ) as u16 ) ,
277+ } ,
278+ match stage {
279+ "1" => Side :: Ancestor ,
280+ "2" => Side :: Ours ,
281+ "3" => Side :: Theirs ,
282+ invalid => panic ! ( "{invalid} is an unexpected side" ) ,
283+ } ,
284+ ) )
285+ }
286+
287+ fn parse_info < ' a > ( mut lines : impl Iterator < Item = & ' a str > ) -> Option < ConflictInfo > {
288+ let num_paths: usize = lines. next ( ) ?. parse ( ) . ok ( ) ?;
289+ let paths: Vec < _ > = lines. by_ref ( ) . take ( num_paths) . map ( ToOwned :: to_owned) . collect ( ) ;
290+ let kind = match lines. next ( ) ? {
291+ "Auto-merging" => ConflictKind :: AutoMerging ,
292+ "CONFLICT (contents)" => ConflictKind :: ConflictContents ,
293+ "CONFLICT (file/directory)" => ConflictKind :: ConflictDirectoryBlocksFile ,
294+ "CONFLICT (modify/delete)" => ConflictKind :: ConflictModifyDelete ,
295+ conflict_type => panic ! ( "Unkonwn conflict type: {conflict_type}" ) ,
296+ } ;
297+ let message = lines. next ( ) ?. to_owned ( ) ;
298+ dbg ! ( & kind, & message) ;
299+ Some ( ConflictInfo { paths, kind, message } )
130300 }
131301
132302 pub fn new_platform (
0 commit comments