@@ -9,7 +9,9 @@ fn run_baseline() -> crate::Result {
9
9
root,
10
10
odb,
11
11
our_commit_id,
12
+ our_side_name,
12
13
their_commit_id,
14
+ their_side_name,
13
15
merge_info,
14
16
case_name,
15
17
} in baseline:: Expectations :: new ( & root, & cases)
@@ -19,56 +21,136 @@ fn run_baseline() -> crate::Result {
19
21
let mut diff_resource_cache = baseline:: new_diff_resource_cache ( & root) ;
20
22
let options = gix_merge:: commit:: Options {
21
23
allow_missing_merge_base : false ,
24
+ use_first_merge_base : false ,
22
25
tree_merge : gix_merge:: tree:: Options {
23
26
rewrites : Some ( Rewrites {
24
27
copies : None ,
25
28
percentage : Some ( 0.5 ) ,
26
29
limit : 0 ,
27
30
} ) ,
31
+ blob_merge : Default :: default ( ) ,
32
+ blob_merge_command_ctx : Default :: default ( ) ,
33
+ fail_on_conflict : false ,
28
34
} ,
29
- blob_merge : Default :: default ( ) ,
30
35
} ;
36
+ dbg ! ( & case_name) ;
31
37
let mut actual = gix_merge:: commit (
32
38
our_commit_id,
33
39
their_commit_id,
34
40
gix_merge:: blob:: builtin_driver:: text:: Labels {
35
41
ancestor : None ,
36
- current : Some ( "ours" . into ( ) ) ,
37
- other : Some ( "theirs" . into ( ) ) ,
42
+ current : Some ( our_side_name . as_str ( ) . into ( ) ) ,
43
+ other : Some ( their_side_name . as_str ( ) . into ( ) ) ,
38
44
} ,
39
45
& mut graph,
40
46
& mut diff_resource_cache,
41
47
& mut blob_merge,
42
48
& odb,
49
+ |content| odb. write_buf ( gix_object:: Kind :: Blob , content) ,
43
50
options,
44
51
) ?;
45
52
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
- }
53
+ let actual_id = actual. tree . write ( |tree| odb. write ( tree) ) ?;
54
+ if actual_id != merge_info. merged_tree {
55
+ baseline:: show_diff_and_fail ( & case_name, actual_id, & actual, & merge_info, & odb) ;
54
56
}
57
+ dbg ! ( actual. conflicts) ;
55
58
}
56
59
57
60
Ok ( ( ) )
58
61
}
59
62
63
+ // TODO: make sure everything is read eventually, even if only to improve debug messages in case of failure.
64
+ #[ allow( dead_code) ]
60
65
mod baseline {
66
+ use bstr:: { BStr , ByteSlice } ;
67
+ use gix_hash:: ObjectId ;
68
+ use gix_object:: tree:: EntryMode ;
69
+ use gix_object:: FindExt ;
61
70
use gix_worktree:: stack:: state:: attributes;
62
71
use std:: path:: { Path , PathBuf } ;
63
72
64
- pub struct Conflict ;
73
+ /// An entry in the conflict
74
+ #[ derive( Debug ) ]
75
+ pub struct Entry {
76
+ /// The relative path in the repository
77
+ pub location : String ,
78
+ /// The content id.
79
+ pub id : gix_hash:: ObjectId ,
80
+ /// The kind of entry.
81
+ pub mode : EntryMode ,
82
+ }
83
+
84
+ /// Keep track of all the sides of a conflict. Some might not be set to indicate removal, including the ancestor.
85
+ #[ derive( Default , Debug ) ]
86
+ pub struct Conflict {
87
+ pub ancestor : Option < Entry > ,
88
+ pub ours : Option < Entry > ,
89
+ pub theirs : Option < Entry > ,
90
+ }
91
+
92
+ #[ derive( Debug ) ]
93
+ pub enum ConflictKind {
94
+ /// The conflict was resolved by automatically merging the content.
95
+ AutoMerging ,
96
+ /// The content could not be resolved so it's conflicting.
97
+ ConflictContents ,
98
+ /// Directory in theirs in the way of our file.
99
+ ConflictDirectoryBlocksFile ,
100
+ /// Modified in ours but deleted in theirs.
101
+ ConflictModifyDelete ,
102
+ /// Modified in ours but parent directory renamed in theirs.
103
+ DirectoryRenamedWithModificationInside ,
104
+ }
105
+
106
+ /// More loosely structured information about the `Conflict`.
107
+ #[ derive( Debug ) ]
108
+ pub struct ConflictInfo {
109
+ /// All the paths involved in the informational message
110
+ pub paths : Vec < String > ,
111
+ /// The type of the conflict, further described in `message`.
112
+ pub kind : ConflictKind ,
113
+ /// An arbitrary message formed from paths and kind
114
+ pub message : String ,
115
+ }
116
+
117
+ impl Conflict {
118
+ fn any_location ( & self ) -> Option < & str > {
119
+ self . ancestor
120
+ . as_ref ( )
121
+ . or ( self . ours . as_ref ( ) )
122
+ . or ( self . theirs . as_ref ( ) )
123
+ . map ( |a| a. location . as_str ( ) )
124
+ }
125
+ fn storage_for ( & mut self , side : Side , location : & str ) -> Option < & mut Option < Entry > > {
126
+ let current_location = self . any_location ( ) ;
127
+ let location_is_same = current_location. is_none ( ) || current_location == Some ( location) ;
128
+ let side = match side {
129
+ Side :: Ancestor => & mut self . ancestor ,
130
+ Side :: Ours => & mut self . ours ,
131
+ Side :: Theirs => & mut self . theirs ,
132
+ } ;
133
+ ( !side. is_some ( ) && location_is_same) . then_some ( side)
134
+ }
135
+ }
136
+
137
+ pub struct MergeInfo {
138
+ /// The hash of the merged tree - it may contain intermediate files if the merge didn't succeed entirely.
139
+ pub merged_tree : gix_hash:: ObjectId ,
140
+ /// If there were conflicts, this is the conflicting paths.
141
+ pub conflicts : Option < Vec < Conflict > > ,
142
+ /// Structured details which to some extent can be compared to our own conflict information.
143
+ pub information : Vec < ConflictInfo > ,
144
+ }
65
145
66
146
pub struct Expectation {
67
147
pub root : PathBuf ,
68
148
pub odb : gix_odb:: memory:: Proxy < gix_odb:: Handle > ,
69
149
pub our_commit_id : gix_hash:: ObjectId ,
150
+ pub our_side_name : String ,
70
151
pub their_commit_id : gix_hash:: ObjectId ,
71
- pub merge_info : Result < gix_hash:: ObjectId , Conflict > ,
152
+ pub their_side_name : String ,
153
+ pub merge_info : MergeInfo ,
72
154
pub case_name : String ,
73
155
}
74
156
@@ -92,8 +174,21 @@ mod baseline {
92
174
fn next ( & mut self ) -> Option < Self :: Item > {
93
175
let line = self . lines . next ( ) ?;
94
176
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 ( ) )
177
+ let (
178
+ Some ( subdir) ,
179
+ Some ( our_commit_id) ,
180
+ Some ( our_side_name) ,
181
+ Some ( their_commit_id) ,
182
+ Some ( their_side_name) ,
183
+ Some ( merge_info_filename) ,
184
+ ) = (
185
+ tokens. next ( ) ,
186
+ tokens. next ( ) ,
187
+ tokens. next ( ) ,
188
+ tokens. next ( ) ,
189
+ tokens. next ( ) ,
190
+ tokens. next ( ) ,
191
+ )
97
192
else {
98
193
unreachable ! ( "invalid line: {line:?}" )
99
194
} ;
@@ -109,7 +204,9 @@ mod baseline {
109
204
root : subdir_path,
110
205
odb : objects,
111
206
our_commit_id,
207
+ our_side_name : our_side_name. to_owned ( ) ,
112
208
their_commit_id,
209
+ their_side_name : their_side_name. to_owned ( ) ,
113
210
merge_info,
114
211
case_name : format ! (
115
212
"{subdir}-{}" ,
@@ -122,11 +219,94 @@ mod baseline {
122
219
}
123
220
}
124
221
125
- fn parse_merge_info ( content : String ) -> Result < gix_hash :: ObjectId , Conflict > {
126
- let mut lines = content. split ( '\0' ) . filter ( |t| !t. is_empty ( ) ) ;
222
+ fn parse_merge_info ( content : String ) -> MergeInfo {
223
+ let mut lines = content. split ( '\0' ) . filter ( |t| !t. is_empty ( ) ) . peekable ( ) ;
127
224
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)
225
+ let mut out = MergeInfo {
226
+ merged_tree : tree_id,
227
+ conflicts : None ,
228
+ information : Vec :: new ( ) ,
229
+ } ;
230
+
231
+ let mut conflicts = Vec :: new ( ) ;
232
+ let mut conflict = Conflict :: default ( ) ;
233
+ while let Some ( line) = lines. peek ( ) {
234
+ let ( entry, side) = match parse_conflict_file_info ( line) {
235
+ Some ( t) => t,
236
+ None => break ,
237
+ } ;
238
+ lines. next ( ) ;
239
+ let field = match conflict. storage_for ( side, & entry. location ) {
240
+ None => {
241
+ conflicts. push ( conflict) ;
242
+ conflict = Conflict :: default ( ) ;
243
+ conflict
244
+ . storage_for ( side, & entry. location )
245
+ . expect ( "always available for new side" )
246
+ }
247
+ Some ( field) => field,
248
+ } ;
249
+ * field = Some ( entry) ;
250
+ }
251
+
252
+ while lines. peek ( ) . is_some ( ) {
253
+ out. information
254
+ . push ( parse_info ( & mut lines) . expect ( "if there are lines, it should be valid info" ) ) ;
255
+ }
256
+ assert_eq ! ( lines. next( ) , None , "TODO: conflict messages" ) ;
257
+ out. conflicts = ( !conflicts. is_empty ( ) ) . then_some ( conflicts) ;
258
+ out
259
+ }
260
+
261
+ #[ derive( Copy , Clone ) ]
262
+ enum Side {
263
+ Ancestor ,
264
+ Ours ,
265
+ Theirs ,
266
+ }
267
+
268
+ fn parse_conflict_file_info ( line : & str ) -> Option < ( Entry , Side ) > {
269
+ let ( info, mut path) = line. split_at ( line. find ( '\t' ) ?) ;
270
+ path = & path[ 1 ..] ;
271
+ let mut tokens = info. split ( ' ' ) ;
272
+ let ( oct_mode, hex_id, stage) = (
273
+ tokens. next ( ) . expect ( "mode" ) ,
274
+ tokens. next ( ) . expect ( "id" ) ,
275
+ tokens. next ( ) . expect ( "stage" ) ,
276
+ ) ;
277
+ assert_eq ! (
278
+ tokens. next( ) ,
279
+ None ,
280
+ "info line not understood, expected three fields only"
281
+ ) ;
282
+ Some ( (
283
+ Entry {
284
+ location : path. to_owned ( ) ,
285
+ id : gix_hash:: ObjectId :: from_hex ( hex_id. as_bytes ( ) ) . unwrap ( ) ,
286
+ mode : EntryMode ( gix_utils:: btoi:: to_signed_with_radix :: < usize > ( oct_mode. as_bytes ( ) , 8 ) . unwrap ( ) as u16 ) ,
287
+ } ,
288
+ match stage {
289
+ "1" => Side :: Ancestor ,
290
+ "2" => Side :: Ours ,
291
+ "3" => Side :: Theirs ,
292
+ invalid => panic ! ( "{invalid} is an unexpected side" ) ,
293
+ } ,
294
+ ) )
295
+ }
296
+
297
+ fn parse_info < ' a > ( mut lines : impl Iterator < Item = & ' a str > ) -> Option < ConflictInfo > {
298
+ let num_paths: usize = lines. next ( ) ?. parse ( ) . ok ( ) ?;
299
+ let paths: Vec < _ > = lines. by_ref ( ) . take ( num_paths) . map ( ToOwned :: to_owned) . collect ( ) ;
300
+ let kind = match lines. next ( ) ? {
301
+ "Auto-merging" => ConflictKind :: AutoMerging ,
302
+ "CONFLICT (contents)" => ConflictKind :: ConflictContents ,
303
+ "CONFLICT (file/directory)" => ConflictKind :: ConflictDirectoryBlocksFile ,
304
+ "CONFLICT (modify/delete)" => ConflictKind :: ConflictModifyDelete ,
305
+ "CONFLICT (directory rename suggested)" => ConflictKind :: DirectoryRenamedWithModificationInside ,
306
+ conflict_type => panic ! ( "Unkonwn conflict type: {conflict_type}" ) ,
307
+ } ;
308
+ let message = lines. next ( ) ?. to_owned ( ) ;
309
+ Some ( ConflictInfo { paths, kind, message } )
130
310
}
131
311
132
312
pub fn new_platform (
@@ -170,4 +350,53 @@ mod baseline {
170
350
) ,
171
351
)
172
352
}
353
+
354
+ fn visualize_tree ( id : & gix_hash:: oid , odb : & impl gix_object:: Find , name : Option < & BStr > ) -> termtree:: Tree < String > {
355
+ fn short_id ( id : & gix_hash:: oid ) -> String {
356
+ id. to_string ( ) [ ..7 ] . to_string ( )
357
+ }
358
+ let entry_name = |id : & gix_hash:: oid , name : Option < & BStr > | -> String {
359
+ let mut buf = Vec :: new ( ) ;
360
+ match name {
361
+ None => short_id ( id) ,
362
+ Some ( name) => {
363
+ format ! (
364
+ "{name}:{} {:?}" ,
365
+ short_id( id) ,
366
+ match odb. find_blob( id, & mut buf) {
367
+ Ok ( blob) => blob. data. as_bstr( ) ,
368
+ Err ( _) => "" . into( ) ,
369
+ }
370
+ )
371
+ }
372
+ }
373
+ } ;
374
+
375
+ let mut tree = termtree:: Tree :: new ( entry_name ( id, name) ) ;
376
+ let mut buf = Vec :: new ( ) ;
377
+ for entry in odb. find_tree ( id, & mut buf) . unwrap ( ) . entries {
378
+ if entry. mode . is_tree ( ) {
379
+ tree. push ( visualize_tree ( entry. oid , odb, entry. filename . into ( ) ) ) ;
380
+ } else {
381
+ tree. push ( entry_name ( entry. oid , entry. filename . into ( ) ) ) ;
382
+ }
383
+ }
384
+ tree
385
+ }
386
+
387
+ pub fn show_diff_and_fail (
388
+ case_name : & str ,
389
+ actual_id : ObjectId ,
390
+ actual : & gix_merge:: tree:: Outcome < ' _ > ,
391
+ expected : & MergeInfo ,
392
+ odb : & gix_odb:: memory:: Proxy < gix_odb:: Handle > ,
393
+ ) {
394
+ pretty_assertions:: assert_str_eq!(
395
+ visualize_tree( & actual_id, odb, None ) . to_string( ) ,
396
+ visualize_tree( & expected. merged_tree, odb, None ) . to_string( ) ,
397
+ "{case_name}: merged tree mismatch {:#?} {:#?}" ,
398
+ actual. conflicts,
399
+ expected. information
400
+ ) ;
401
+ }
173
402
}
0 commit comments