@@ -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,50 +27,123 @@ fn run_baseline() -> crate::Result {
2527 percentage : Some ( 0.5 ) ,
2628 limit : 0 ,
2729 } ) ,
30+ blob_merge : Default :: default ( ) ,
31+ blob_merge_command_ctx : Default :: default ( ) ,
2832 } ,
29- blob_merge : Default :: default ( ) ,
3033 } ;
3134 let mut actual = gix_merge:: commit (
3235 our_commit_id,
3336 their_commit_id,
3437 gix_merge:: blob:: builtin_driver:: text:: Labels {
3538 ancestor : None ,
36- current : Some ( "ours" . into ( ) ) ,
37- other : Some ( "theirs" . into ( ) ) ,
39+ current : Some ( our_side_name . as_str ( ) . into ( ) ) ,
40+ other : Some ( their_side_name . as_str ( ) . into ( ) ) ,
3841 } ,
3942 & mut graph,
4043 & mut diff_resource_cache,
4144 & mut blob_merge,
4245 & odb,
46+ |content| odb. write_buf ( gix_object:: Kind :: Blob , content) ,
4347 options,
4448 ) ?;
4549
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- }
50+ let actual_id = actual. tree . write ( |tree| odb. write ( tree) ) ?;
51+ assert_eq ! ( actual_id, merge_info. merged_tree, "{case_name}: merged tree mismatch" ) ;
52+ if let Some ( conflicts) = merge_info. conflicts {
53+ dbg ! ( & conflicts, & merge_info. information) ;
54+ todo ! ( "compare merge conflict information" )
5455 }
5556 }
5657
5758 Ok ( ( ) )
5859}
5960
61+ // TODO: make sure everything is read eventually, even if only to improve debug messages in case of failure.
62+ #[ allow( dead_code) ]
6063mod baseline {
64+ use gix_object:: tree:: EntryMode ;
6165 use gix_worktree:: stack:: state:: attributes;
6266 use std:: path:: { Path , PathBuf } ;
6367
64- pub struct Conflict ;
68+ /// An entry in the conflict
69+ #[ derive( Debug ) ]
70+ pub struct Entry {
71+ /// The relative path in the repository
72+ pub location : String ,
73+ /// The content id.
74+ pub id : gix_hash:: ObjectId ,
75+ /// The kind of entry.
76+ pub mode : EntryMode ,
77+ }
78+
79+ /// Keep track of all the sides of a conflict. Some might not be set to indicate removal, including the ancestor.
80+ #[ derive( Default , Debug ) ]
81+ pub struct Conflict {
82+ pub ancestor : Option < Entry > ,
83+ pub ours : Option < Entry > ,
84+ pub theirs : Option < Entry > ,
85+ }
86+
87+ #[ derive( Debug ) ]
88+ pub enum ConflictKind {
89+ /// The conflict was resolved by automatically merging the content.
90+ AutoMerging ,
91+ /// The content could not be resolved so it's conflicting.
92+ ConflictContents ,
93+ /// Directory in theirs in the way of our file
94+ ConflictDirectoryBlocksFile ,
95+ /// Modified in ours but deleted in theirs
96+ ConflictModifyDelete ,
97+ }
98+
99+ /// More loosely structured information about the `Conflict`.
100+ #[ derive( Debug ) ]
101+ pub struct ConflictInfo {
102+ /// All the paths involved in the informational message
103+ pub paths : Vec < String > ,
104+ /// The type of the conflict, further described in `message`.
105+ pub kind : ConflictKind ,
106+ /// An arbitrary message formed from paths and kind
107+ pub message : String ,
108+ }
109+
110+ impl Conflict {
111+ fn any_location ( & self ) -> Option < & str > {
112+ self . ancestor
113+ . as_ref ( )
114+ . or ( self . ours . as_ref ( ) )
115+ . or ( self . theirs . as_ref ( ) )
116+ . map ( |a| a. location . as_str ( ) )
117+ }
118+ fn storage_for ( & mut self , side : Side , location : & str ) -> Option < & mut Option < Entry > > {
119+ let current_location = self . any_location ( ) ;
120+ let location_is_same = current_location. is_none ( ) || current_location == Some ( location) ;
121+ let side = match side {
122+ Side :: Ancestor => & mut self . ancestor ,
123+ Side :: Ours => & mut self . ours ,
124+ Side :: Theirs => & mut self . theirs ,
125+ } ;
126+ ( !side. is_some ( ) && location_is_same) . then_some ( side)
127+ }
128+ }
129+
130+ pub struct MergeInfo {
131+ /// The hash of the merged tree - it may contain intermediate files if the merge didn't succeed entirely.
132+ pub merged_tree : gix_hash:: ObjectId ,
133+ /// If there were conflicts, this is the conflicting paths.
134+ pub conflicts : Option < Vec < Conflict > > ,
135+ /// Structured details which to some extent can be compared to our own conflict information.
136+ pub information : Vec < ConflictInfo > ,
137+ }
65138
66139 pub struct Expectation {
67140 pub root : PathBuf ,
68141 pub odb : gix_odb:: memory:: Proxy < gix_odb:: Handle > ,
69142 pub our_commit_id : gix_hash:: ObjectId ,
143+ pub our_side_name : String ,
70144 pub their_commit_id : gix_hash:: ObjectId ,
71- pub merge_info : Result < gix_hash:: ObjectId , Conflict > ,
145+ pub their_side_name : String ,
146+ pub merge_info : MergeInfo ,
72147 pub case_name : String ,
73148 }
74149
@@ -92,8 +167,21 @@ mod baseline {
92167 fn next ( & mut self ) -> Option < Self :: Item > {
93168 let line = self . lines . next ( ) ?;
94169 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 ( ) )
170+ let (
171+ Some ( subdir) ,
172+ Some ( our_commit_id) ,
173+ Some ( our_side_name) ,
174+ Some ( their_commit_id) ,
175+ Some ( their_side_name) ,
176+ Some ( merge_info_filename) ,
177+ ) = (
178+ tokens. next ( ) ,
179+ tokens. next ( ) ,
180+ tokens. next ( ) ,
181+ tokens. next ( ) ,
182+ tokens. next ( ) ,
183+ tokens. next ( ) ,
184+ )
97185 else {
98186 unreachable ! ( "invalid line: {line:?}" )
99187 } ;
@@ -109,7 +197,9 @@ mod baseline {
109197 root : subdir_path,
110198 odb : objects,
111199 our_commit_id,
200+ our_side_name : our_side_name. to_owned ( ) ,
112201 their_commit_id,
202+ their_side_name : their_side_name. to_owned ( ) ,
113203 merge_info,
114204 case_name : format ! (
115205 "{subdir}-{}" ,
@@ -122,11 +212,93 @@ mod baseline {
122212 }
123213 }
124214
125- fn parse_merge_info ( content : String ) -> Result < gix_hash :: ObjectId , Conflict > {
126- let mut lines = content. split ( '\0' ) . filter ( |t| !t. is_empty ( ) ) ;
215+ fn parse_merge_info ( content : String ) -> MergeInfo {
216+ let mut lines = content. split ( '\0' ) . filter ( |t| !t. is_empty ( ) ) . peekable ( ) ;
127217 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)
218+ let mut out = MergeInfo {
219+ merged_tree : tree_id,
220+ conflicts : None ,
221+ information : Vec :: new ( ) ,
222+ } ;
223+
224+ let mut conflicts = Vec :: new ( ) ;
225+ let mut conflict = Conflict :: default ( ) ;
226+ while let Some ( line) = lines. peek ( ) {
227+ let ( entry, side) = match parse_conflict_file_info ( line) {
228+ Some ( t) => t,
229+ None => break ,
230+ } ;
231+ lines. next ( ) ;
232+ let field = match conflict. storage_for ( side, & entry. location ) {
233+ None => {
234+ conflicts. push ( conflict) ;
235+ conflict = Conflict :: default ( ) ;
236+ conflict
237+ . storage_for ( side, & entry. location )
238+ . expect ( "always available for new side" )
239+ }
240+ Some ( field) => field,
241+ } ;
242+ * field = Some ( entry) ;
243+ }
244+
245+ while lines. peek ( ) . is_some ( ) {
246+ out. information
247+ . push ( parse_info ( & mut lines) . expect ( "if there are lines, it should be valid info" ) ) ;
248+ }
249+ assert_eq ! ( lines. next( ) , None , "TODO: conflict messages" ) ;
250+ out. conflicts = ( !conflicts. is_empty ( ) ) . then_some ( conflicts) ;
251+ out
252+ }
253+
254+ #[ derive( Copy , Clone ) ]
255+ enum Side {
256+ Ancestor ,
257+ Ours ,
258+ Theirs ,
259+ }
260+
261+ fn parse_conflict_file_info ( line : & str ) -> Option < ( Entry , Side ) > {
262+ let ( info, path) = line. split_at ( line. find ( '\t' ) ?) ;
263+ let mut tokens = info. split ( ' ' ) ;
264+ let ( oct_mode, hex_id, stage) = (
265+ tokens. next ( ) . expect ( "mode" ) ,
266+ tokens. next ( ) . expect ( "id" ) ,
267+ tokens. next ( ) . expect ( "stage" ) ,
268+ ) ;
269+ assert_eq ! (
270+ tokens. next( ) ,
271+ None ,
272+ "info line not understood, expected three fields only"
273+ ) ;
274+ Some ( (
275+ Entry {
276+ location : path. to_owned ( ) ,
277+ id : gix_hash:: ObjectId :: from_hex ( hex_id. as_bytes ( ) ) . unwrap ( ) ,
278+ mode : EntryMode ( gix_utils:: btoi:: to_signed_with_radix :: < usize > ( oct_mode. as_bytes ( ) , 8 ) . unwrap ( ) as u16 ) ,
279+ } ,
280+ match stage {
281+ "1" => Side :: Ancestor ,
282+ "2" => Side :: Ours ,
283+ "3" => Side :: Theirs ,
284+ invalid => panic ! ( "{invalid} is an unexpected side" ) ,
285+ } ,
286+ ) )
287+ }
288+
289+ fn parse_info < ' a > ( mut lines : impl Iterator < Item = & ' a str > ) -> Option < ConflictInfo > {
290+ let num_paths: usize = lines. next ( ) ?. parse ( ) . ok ( ) ?;
291+ let paths: Vec < _ > = lines. by_ref ( ) . take ( num_paths) . map ( ToOwned :: to_owned) . collect ( ) ;
292+ let kind = match lines. next ( ) ? {
293+ "Auto-merging" => ConflictKind :: AutoMerging ,
294+ "CONFLICT (contents)" => ConflictKind :: ConflictContents ,
295+ "CONFLICT (file/directory)" => ConflictKind :: ConflictDirectoryBlocksFile ,
296+ "CONFLICT (modify/delete)" => ConflictKind :: ConflictModifyDelete ,
297+ conflict_type => panic ! ( "Unkonwn conflict type: {conflict_type}" ) ,
298+ } ;
299+ let message = lines. next ( ) ?. to_owned ( ) ;
300+ dbg ! ( & kind, & message) ;
301+ Some ( ConflictInfo { paths, kind, message } )
130302 }
131303
132304 pub fn new_platform (
0 commit comments