1use crate::record::{Body, MergeClass};
5
6#[derive(Clone, Debug, PartialEq, Eq)]
8#[must_use = "a MergeOutcome may need caller resolution and must not be ignored"]
9pub enum MergeOutcome {
10 Merged(Body),
12 NeedsResolution,
14}
15
16pub fn merge(class: MergeClass, base: &Body, ours: &Body, theirs: &Body) -> MergeOutcome {
34 match class {
35 MergeClass::AppendOnly => append_only_merge(base, ours, theirs),
36 MergeClass::Structured => structured_merge(base, ours, theirs),
37 MergeClass::Opaque => MergeOutcome::NeedsResolution,
38 MergeClass::Derived => MergeOutcome::Merged(ours.clone()),
39 }
40}
41
42fn structured_merge(base: &Body, ours: &Body, theirs: &Body) -> MergeOutcome {
51 let (Ok(base), Ok(ours), Ok(theirs)) = (
52 serde_json::from_slice::<serde_json::Value>(base.bytes()),
53 serde_json::from_slice::<serde_json::Value>(ours.bytes()),
54 serde_json::from_slice::<serde_json::Value>(theirs.bytes()),
55 ) else {
56 return MergeOutcome::NeedsResolution;
57 };
58 match merge_value(&base, &ours, &theirs) {
59 Some(merged) => match serde_json::to_vec(&merged) {
60 Ok(bytes) => MergeOutcome::Merged(Body::Inline(bytes)),
61 Err(_) => MergeOutcome::NeedsResolution,
62 },
63 None => MergeOutcome::NeedsResolution,
64 }
65}
66
67fn merge_value(
73 base: &serde_json::Value,
74 ours: &serde_json::Value,
75 theirs: &serde_json::Value,
76) -> Option<serde_json::Value> {
77 if ours == theirs {
79 return Some(ours.clone());
80 }
81 if ours == base {
83 return Some(theirs.clone());
84 }
85 if theirs == base {
86 return Some(ours.clone());
87 }
88 match (base.as_object(), ours.as_object(), theirs.as_object()) {
91 (Some(base_obj), Some(ours_obj), Some(theirs_obj)) => {
92 let mut out = serde_json::Map::new();
93 let mut keys: Vec<&String> = base_obj
94 .keys()
95 .chain(ours_obj.keys())
96 .chain(theirs_obj.keys())
97 .collect();
98 keys.sort();
99 keys.dedup();
100 for key in keys {
101 if let Some(v) =
104 merge_field(base_obj.get(key), ours_obj.get(key), theirs_obj.get(key))?
105 {
106 out.insert(key.clone(), v);
107 }
108 }
109 Some(serde_json::Value::Object(out))
110 }
111 _ => None,
112 }
113}
114
115fn merge_field(
120 base: Option<&serde_json::Value>,
121 ours: Option<&serde_json::Value>,
122 theirs: Option<&serde_json::Value>,
123) -> Option<Option<serde_json::Value>> {
124 if ours == theirs {
126 return Some(ours.cloned());
127 }
128 if ours == base {
130 return Some(theirs.cloned());
131 }
132 if theirs == base {
133 return Some(ours.cloned());
134 }
135 match (ours, theirs) {
138 (Some(o), Some(t)) => {
139 let b = base.unwrap_or(&serde_json::Value::Null);
140 merge_value(b, o, t).map(Some)
141 }
142 _ => None,
143 }
144}
145
146fn append_only_merge(_base: &Body, ours: &Body, theirs: &Body) -> MergeOutcome {
159 let ours_lines = split_lines(ours.bytes());
160 let theirs_lines = split_lines(theirs.bytes());
161 let shared = ours_lines
166 .iter()
167 .zip(theirs_lines.iter())
168 .take_while(|(o, t)| strip_nl(o) == strip_nl(t))
169 .count();
170
171 let mut out: Vec<u8> = ours.bytes().to_vec();
172 let tail = &theirs_lines[shared..];
173 if !tail.is_empty() && out.last().is_some_and(|&b| b != b'\n') {
176 out.push(b'\n');
177 }
178 for line in tail {
179 out.extend_from_slice(line);
180 }
181 MergeOutcome::Merged(Body::Inline(out))
182}
183
184fn strip_nl(line: &[u8]) -> &[u8] {
186 line.strip_suffix(b"\n").unwrap_or(line)
187}
188
189fn split_lines(bytes: &[u8]) -> Vec<&[u8]> {
193 let mut lines = Vec::new();
194 let mut start = 0;
195 for (i, &b) in bytes.iter().enumerate() {
196 if b == b'\n' {
197 lines.push(&bytes[start..=i]);
198 start = i + 1;
199 }
200 }
201 if start < bytes.len() {
202 lines.push(&bytes[start..]);
203 }
204 lines
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 fn body(s: &str) -> Body {
212 Body::Inline(s.as_bytes().to_vec())
213 }
214
215 #[test]
216 fn append_only_unions_disjoint_additions() {
217 let base = body("a\n");
218 let ours = body("a\nb\n");
219 let theirs = body("a\nc\n");
220 let MergeOutcome::Merged(m) = merge(MergeClass::AppendOnly, &base, &ours, &theirs) else {
221 panic!("expected merge");
222 };
223 assert_eq!(m, body("a\nb\nc\n"));
224 }
225
226 #[test]
227 fn append_only_identical_sides_yield_ours() {
228 let base = body("a\n");
231 let ours = body("a\nb\n");
232 let theirs = body("a\nb\n");
233 let MergeOutcome::Merged(m) = merge(MergeClass::AppendOnly, &base, &ours, &theirs) else {
234 panic!("expected merge");
235 };
236 assert_eq!(m, body("a\nb\n"));
237 }
238
239 #[test]
240 fn append_only_appends_theirs_tail_and_preserves_repeats() {
241 let base = body("a\nb\n");
248 let ours = body("a\nb\nc\nc\nd\n");
249 let theirs = body("a\nb\ne\nf\n");
250 let MergeOutcome::Merged(m) = merge(MergeClass::AppendOnly, &base, &ours, &theirs) else {
251 panic!("expected merge");
252 };
253 assert_eq!(m, body("a\nb\nc\nc\nd\ne\nf\n"));
254 }
255
256 #[test]
257 fn append_only_preserves_blank_and_duplicate_lines() {
258 let base = body("");
265 let ours = body("a\n\nyes\nyes\nfrom_a\n");
266 let theirs = body("a\n\nyes\nyes\nfrom_b\n");
267 let MergeOutcome::Merged(m) = merge(MergeClass::AppendOnly, &base, &ours, &theirs) else {
268 panic!("expected merge");
269 };
270 assert_eq!(m, body("a\n\nyes\nyes\nfrom_a\nfrom_b\n"));
271 }
272
273 #[test]
274 fn append_only_handles_non_newline_terminated_ours() {
275 let base = body("");
279 let ours = body("a\nb");
280 let theirs = body("a\nb\nc\n");
281 let MergeOutcome::Merged(m) = merge(MergeClass::AppendOnly, &base, &ours, &theirs) else {
282 panic!("expected merge");
283 };
284 assert_eq!(m, body("a\nb\nc\n"));
285
286 let ours2 = body("a\nx");
289 let theirs2 = body("a\ny\nz\n");
290 let MergeOutcome::Merged(m2) = merge(MergeClass::AppendOnly, &base, &ours2, &theirs2)
291 else {
292 panic!("expected merge");
293 };
294 assert_eq!(m2, body("a\nx\ny\nz\n"));
295 }
296
297 #[test]
298 fn opaque_needs_resolution() {
299 let b = body("x\n");
300 assert_eq!(
301 merge(MergeClass::Opaque, &b, &b, &b),
302 MergeOutcome::NeedsResolution
303 );
304 }
305
306 #[test]
307 fn derived_takes_side_a_deterministically() {
308 let base = body("base");
315 let ours = body("ours-version");
316 let theirs = body("theirs-version");
317 let MergeOutcome::Merged(m) = merge(MergeClass::Derived, &base, &ours, &theirs) else {
318 panic!("Derived must merge, never NeedsResolution");
319 };
320 assert_eq!(m, ours);
321 let MergeOutcome::Merged(swapped) = merge(MergeClass::Derived, &base, &theirs, &ours)
323 else {
324 panic!("Derived must merge, never NeedsResolution");
325 };
326 assert_eq!(swapped, theirs);
327 }
328
329 fn structured(base: &str, ours: &str, theirs: &str) -> serde_json::Value {
332 match merge(
333 MergeClass::Structured,
334 &body(base),
335 &body(ours),
336 &body(theirs),
337 ) {
338 MergeOutcome::Merged(b) => serde_json::from_slice(b.bytes()).unwrap(),
339 MergeOutcome::NeedsResolution => panic!("expected a merge, got NeedsResolution"),
340 }
341 }
342
343 fn structured_conflicts(base: &str, ours: &str, theirs: &str) -> bool {
344 matches!(
345 merge(
346 MergeClass::Structured,
347 &body(base),
348 &body(ours),
349 &body(theirs),
350 ),
351 MergeOutcome::NeedsResolution
352 )
353 }
354
355 #[test]
356 fn structured_merges_disjoint_field_edits() {
357 let merged = structured(
359 r#"{"name":"a","content":"x"}"#,
360 r#"{"name":"b","content":"x"}"#,
361 r#"{"name":"a","content":"y"}"#,
362 );
363 assert_eq!(merged, serde_json::json!({"name":"b","content":"y"}));
364 }
365
366 #[test]
367 fn structured_takes_the_only_changed_side() {
368 let merged = structured(r#"{"k":1,"j":2}"#, r#"{"k":1,"j":2}"#, r#"{"k":9,"j":2}"#);
370 assert_eq!(merged, serde_json::json!({"k":9,"j":2}));
371 }
372
373 #[test]
374 fn structured_adds_new_keys_from_both_sides() {
375 let merged = structured(r#"{"a":1}"#, r#"{"a":1,"b":2}"#, r#"{"a":1,"c":3}"#);
376 assert_eq!(merged, serde_json::json!({"a":1,"b":2,"c":3}));
377 }
378
379 #[test]
380 fn structured_conflicts_on_same_field_changed_differently() {
381 assert!(structured_conflicts(
382 r#"{"k":1}"#,
383 r#"{"k":2}"#,
384 r#"{"k":3}"#,
385 ));
386 }
387
388 #[test]
389 fn structured_merges_nested_objects() {
390 let merged = structured(
392 r#"{"meta":{"a":1,"b":2}}"#,
393 r#"{"meta":{"a":9,"b":2}}"#,
394 r#"{"meta":{"a":1,"b":8}}"#,
395 );
396 assert_eq!(merged, serde_json::json!({"meta":{"a":9,"b":8}}));
397 }
398
399 #[test]
400 fn structured_conflicts_on_nested_same_field() {
401 assert!(structured_conflicts(
402 r#"{"meta":{"a":1}}"#,
403 r#"{"meta":{"a":2}}"#,
404 r#"{"meta":{"a":3}}"#,
405 ));
406 }
407
408 #[test]
409 fn structured_honors_one_sided_deletion() {
410 let merged = structured(r#"{"a":1,"b":2}"#, r#"{"a":1}"#, r#"{"a":1,"b":2}"#);
412 assert_eq!(merged, serde_json::json!({"a":1}));
413 }
414
415 #[test]
416 fn structured_conflicts_on_modify_delete() {
417 assert!(structured_conflicts(
419 r#"{"a":1,"b":2}"#,
420 r#"{"a":1}"#,
421 r#"{"a":1,"b":9}"#,
422 ));
423 }
424
425 #[test]
426 fn structured_arrays_merge_atomically() {
427 let merged = structured(r#"{"xs":[1]}"#, r#"{"xs":[1,2]}"#, r#"{"xs":[1,2]}"#);
429 assert_eq!(merged, serde_json::json!({"xs":[1,2]}));
430 assert!(structured_conflicts(
432 r#"{"xs":[1]}"#,
433 r#"{"xs":[1,2]}"#,
434 r#"{"xs":[1,3]}"#,
435 ));
436 }
437
438 #[test]
439 fn structured_non_json_falls_back_to_needs_resolution() {
440 assert!(structured_conflicts("not json", "also not", "nope"));
441 }
442
443 #[test]
444 fn structured_non_object_root_conflict_needs_resolution() {
445 assert!(structured_conflicts("1", "2", "3"));
447 }
448}