@@ -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) 
@@ -33,8 +35,8 @@ fn run_baseline() -> crate::Result {
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