1use std::collections::HashMap;
18use std::collections::HashSet;
19
20use crate::hash::Hash;
21use crate::object::{EntryMode, Object, Tree, TreeEntry};
22use crate::serialize;
23use crate::store::{MAX_TREE_DEPTH, ObjectStore, StoreError};
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
27pub enum ConflictKind {
28 ModifyModify,
30 DeleteModify,
32 AddAdd,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct Conflict {
42 pub path: String,
43 pub kind: ConflictKind,
44 pub base_hash: Option<Hash>,
45 pub ours_hash: Option<Hash>,
46 pub theirs_hash: Option<Hash>,
47 pub ours_mode: Option<EntryMode>,
52 pub theirs_mode: Option<EntryMode>,
55}
56
57#[derive(Debug, Clone, PartialEq, Eq)]
62pub struct MergeResult {
63 pub tree_hash: Hash,
64 pub conflicts: Vec<Conflict>,
65}
66
67impl MergeResult {
68 #[must_use]
69 pub fn has_conflicts(&self) -> bool {
70 !self.conflicts.is_empty()
71 }
72}
73
74pub fn merge_trees(
84 store: &ObjectStore,
85 base_hash: Option<Hash>,
86 ours_hash: Option<Hash>,
87 theirs_hash: Option<Hash>,
88) -> Result<MergeResult, StoreError> {
89 let base_entries = load_entries(store, base_hash)?;
90 let ours_entries = load_entries(store, ours_hash)?;
91 let theirs_entries = load_entries(store, theirs_hash)?;
92
93 let mut merged: Vec<TreeEntry> = Vec::new();
94 let mut conflicts: Vec<Conflict> = Vec::new();
95 merge_entries_recursive(
96 store,
97 &base_entries,
98 &ours_entries,
99 &theirs_entries,
100 "",
101 &mut merged,
102 &mut conflicts,
103 0,
104 )?;
105
106 let tree_hash = put_tree(store, merged)?;
107 Ok(MergeResult {
108 tree_hash,
109 conflicts,
110 })
111}
112
113pub fn find_merge_base(store: &ObjectStore, a: Hash, b: Hash) -> Result<Option<Hash>, StoreError> {
128 if a == b {
129 return Ok(Some(a));
130 }
131 let ancestors_a = collect_ancestors_with_depth(store, a)?;
132
133 let mut queue: Vec<(Hash, usize)> = Vec::new();
135 queue.push((b, 0));
136
137 let mut best: Option<(Hash, usize)> = None;
138 let mut head = 0usize;
139 while head < queue.len() {
140 let (node, depth) = queue[head];
141 head += 1;
142 if let Some((_, best_total)) = best
143 && depth > best_total
144 {
145 break;
146 }
147 if let Some(&ancestor_depth) = ancestors_a.get(&node) {
148 let total = ancestor_depth + depth;
149 match best {
150 None => best = Some((node, total)),
151 Some((_, t)) if total < t => best = Some((node, total)),
152 _ => {}
153 }
154 }
155 match store.read_object(&node) {
156 Ok(Object::Commit(c)) => {
157 for &p in &c.parents {
158 queue.push((p, depth + 1));
159 }
160 }
161 Ok(_) | Err(StoreError::ObjectNotFound(_)) => {}
162 Err(e) => return Err(e),
163 }
164 }
165 Ok(best.map(|(h, _)| h))
166}
167
168pub fn is_ancestor(
176 store: &ObjectStore,
177 ancestor: Hash,
178 descendant: Hash,
179) -> Result<bool, StoreError> {
180 if ancestor == descendant {
181 return Ok(true);
182 }
183 let mut seen: HashSet<Hash> = HashSet::new();
184 let mut stack: Vec<Hash> = Vec::new();
185 stack.push(descendant);
186
187 while let Some(current) = stack.pop() {
188 if !seen.insert(current) {
189 continue;
190 }
191 if current == ancestor {
192 return Ok(true);
193 }
194 match store.read_object(¤t) {
195 Ok(Object::Commit(c)) => {
196 for &p in &c.parents {
197 stack.push(p);
198 }
199 }
200 Ok(_) | Err(StoreError::ObjectNotFound(_)) => {}
201 Err(e) => return Err(e),
202 }
203 }
204 Ok(false)
205}
206
207fn load_entries(store: &ObjectStore, hash: Option<Hash>) -> Result<Vec<TreeEntry>, StoreError> {
212 match hash {
213 Some(h) => match store.read_object(&h)? {
214 Object::Tree(t) => Ok(t.entries),
215 other => Err(StoreError::Decode(
216 crate::object::MkitError::InvalidObjectType(other.object_type() as u8),
217 )),
218 },
219 None => Ok(Vec::new()),
220 }
221}
222
223fn put_tree(store: &ObjectStore, entries: Vec<TreeEntry>) -> Result<Hash, StoreError> {
224 let bytes = serialize::serialize(&Object::Tree(Tree { entries }))?;
225 store.write(&bytes)
226}
227
228fn single_blob_bytes(store: &ObjectStore, h: Hash) -> Result<Option<Vec<u8>>, StoreError> {
234 match store.read_object(&h)? {
235 Object::Blob(b) => Ok(Some(b.data)),
236 _ => Ok(None),
237 }
238}
239
240fn try_text_merge(
246 store: &ObjectStore,
247 base_h: Hash,
248 ours_h: Hash,
249 theirs_h: Hash,
250) -> Result<Option<Hash>, StoreError> {
251 let (Some(base), Some(ours), Some(theirs)) = (
252 single_blob_bytes(store, base_h)?,
253 single_blob_bytes(store, ours_h)?,
254 single_blob_bytes(store, theirs_h)?,
255 ) else {
256 return Ok(None);
257 };
258 match crate::ops::merge_blob_3way(&base, &ours, &theirs) {
259 Some(merged) => {
260 let bytes = serialize::serialize(&Object::Blob(crate::object::Blob { data: merged }))?;
263 Ok(Some(store.write(&bytes)?))
264 }
265 None => Ok(None),
266 }
267}
268
269fn collect_ancestors_with_depth(
270 store: &ObjectStore,
271 start: Hash,
272) -> Result<HashMap<Hash, usize>, StoreError> {
273 let mut ancestors: HashMap<Hash, usize> = HashMap::new();
274 let mut queue: Vec<(Hash, usize)> = Vec::new();
275 queue.push((start, 0));
276
277 let mut head = 0usize;
278 while head < queue.len() {
279 let (node, depth) = queue[head];
280 head += 1;
281 if ancestors.contains_key(&node) {
283 continue;
284 }
285 ancestors.insert(node, depth);
286 match store.read_object(&node) {
287 Ok(Object::Commit(c)) => {
288 for &p in &c.parents {
289 queue.push((p, depth + 1));
290 }
291 }
292 Ok(_) | Err(StoreError::ObjectNotFound(_)) => {}
293 Err(e) => return Err(e),
294 }
295 }
296 Ok(ancestors)
297}
298
299fn hash_and_mode_eq(a: &TreeEntry, b: &TreeEntry) -> bool {
300 a.mode == b.mode && a.object_hash == b.object_hash
301}
302
303fn min_of_three<'a>(
304 a: Option<&'a [u8]>,
305 b: Option<&'a [u8]>,
306 c: Option<&'a [u8]>,
307) -> Option<&'a [u8]> {
308 let mut result: Option<&'a [u8]> = a;
309 if let Some(bv) = b {
310 result = match result {
311 Some(r) if r <= bv => Some(r),
312 _ => Some(bv),
313 };
314 }
315 if let Some(cv) = c {
316 result = match result {
317 Some(r) if r <= cv => Some(r),
318 _ => Some(cv),
319 };
320 }
321 result
322}
323
324fn join_path(prefix: &str, name: &[u8]) -> String {
325 let name_str = String::from_utf8_lossy(name);
326 if prefix.is_empty() {
327 name_str.into_owned()
328 } else {
329 let mut s = String::with_capacity(prefix.len() + 1 + name_str.len());
330 s.push_str(prefix);
331 s.push('/');
332 s.push_str(&name_str);
333 s
334 }
335}
336
337fn add_entry(out: &mut Vec<TreeEntry>, name: &[u8], mode: EntryMode, object_hash: Hash) {
338 out.push(TreeEntry {
339 name: name.to_vec(),
340 mode,
341 object_hash,
342 });
343}
344
345#[allow(clippy::too_many_arguments)]
346fn recurse_subtree_merge(
347 store: &ObjectStore,
348 base_sub: Option<Hash>,
349 ours_sub: Option<Hash>,
350 theirs_sub: Option<Hash>,
351 entry_name: &[u8],
352 prefix: &str,
353 merged: &mut Vec<TreeEntry>,
354 conflicts: &mut Vec<Conflict>,
355 depth: usize,
356) -> Result<(), StoreError> {
357 let sub_prefix = join_path(prefix, entry_name);
358 let base_entries = load_entries(store, base_sub)?;
359 let ours_entries = load_entries(store, ours_sub)?;
360 let theirs_entries = load_entries(store, theirs_sub)?;
361
362 let mut sub_merged: Vec<TreeEntry> = Vec::new();
363 merge_entries_recursive(
364 store,
365 &base_entries,
366 &ours_entries,
367 &theirs_entries,
368 &sub_prefix,
369 &mut sub_merged,
370 conflicts,
371 depth + 1,
372 )?;
373
374 let sub_hash = put_tree(store, sub_merged)?;
375 add_entry(merged, entry_name, EntryMode::Tree, sub_hash);
376 Ok(())
377}
378
379#[allow(clippy::too_many_lines)]
380fn merge_entries_recursive(
381 store: &ObjectStore,
382 base_entries: &[TreeEntry],
383 ours_entries: &[TreeEntry],
384 theirs_entries: &[TreeEntry],
385 prefix: &str,
386 merged: &mut Vec<TreeEntry>,
387 conflicts: &mut Vec<Conflict>,
388 depth: usize,
389) -> Result<(), StoreError> {
390 if depth > MAX_TREE_DEPTH {
391 return Err(StoreError::TreeTooDeep);
392 }
393 let mut bi = 0usize;
394 let mut oi = 0usize;
395 let mut ti = 0usize;
396
397 while bi < base_entries.len() || oi < ours_entries.len() || ti < theirs_entries.len() {
398 let b_name: Option<&[u8]> = base_entries.get(bi).map(|e| e.name.as_slice());
399 let o_name: Option<&[u8]> = ours_entries.get(oi).map(|e| e.name.as_slice());
400 let t_name: Option<&[u8]> = theirs_entries.get(ti).map(|e| e.name.as_slice());
401
402 let Some(min_name) = min_of_three(b_name, o_name, t_name) else {
403 break;
404 };
405
406 let has_base = b_name.is_some_and(|n| n == min_name);
407 let has_ours = o_name.is_some_and(|n| n == min_name);
408 let has_theirs = t_name.is_some_and(|n| n == min_name);
409
410 let base_entry = if has_base {
411 Some(&base_entries[bi])
412 } else {
413 None
414 };
415 let ours_entry = if has_ours {
416 Some(&ours_entries[oi])
417 } else {
418 None
419 };
420 let theirs_entry = if has_theirs {
421 Some(&theirs_entries[ti])
422 } else {
423 None
424 };
425
426 if has_base {
427 bi += 1;
428 }
429 if has_ours {
430 oi += 1;
431 }
432 if has_theirs {
433 ti += 1;
434 }
435
436 match (has_base, has_ours, has_theirs) {
437 (true, true, true) => {
438 let b = base_entry.unwrap();
439 let o = ours_entry.unwrap();
440 let t = theirs_entry.unwrap();
441 let b_eq_o = hash_and_mode_eq(b, o);
442 let b_eq_t = hash_and_mode_eq(b, t);
443 let o_eq_t = hash_and_mode_eq(o, t);
444 if b_eq_o && b_eq_t {
445 add_entry(merged, min_name, b.mode, b.object_hash);
446 } else if b_eq_t && !b_eq_o {
447 if b.mode == EntryMode::Tree && o.mode == EntryMode::Tree {
449 recurse_subtree_merge(
450 store,
451 Some(b.object_hash),
452 Some(o.object_hash),
453 Some(b.object_hash),
454 min_name,
455 prefix,
456 merged,
457 conflicts,
458 depth,
459 )?;
460 } else {
461 add_entry(merged, min_name, o.mode, o.object_hash);
462 }
463 } else if b_eq_o && !b_eq_t {
464 if b.mode == EntryMode::Tree && t.mode == EntryMode::Tree {
466 recurse_subtree_merge(
467 store,
468 Some(b.object_hash),
469 Some(b.object_hash),
470 Some(t.object_hash),
471 min_name,
472 prefix,
473 merged,
474 conflicts,
475 depth,
476 )?;
477 } else {
478 add_entry(merged, min_name, t.mode, t.object_hash);
479 }
480 } else if o_eq_t {
481 if b.mode == EntryMode::Tree && o.mode == EntryMode::Tree {
483 recurse_subtree_merge(
484 store,
485 Some(b.object_hash),
486 Some(o.object_hash),
487 Some(t.object_hash),
488 min_name,
489 prefix,
490 merged,
491 conflicts,
492 depth,
493 )?;
494 } else {
495 add_entry(merged, min_name, o.mode, o.object_hash);
496 }
497 } else if b.mode == EntryMode::Tree
498 && o.mode == EntryMode::Tree
499 && t.mode == EntryMode::Tree
500 {
501 recurse_subtree_merge(
503 store,
504 Some(b.object_hash),
505 Some(o.object_hash),
506 Some(t.object_hash),
507 min_name,
508 prefix,
509 merged,
510 conflicts,
511 depth,
512 )?;
513 } else if o.mode == t.mode
514 && matches!(o.mode, EntryMode::Blob | EntryMode::Executable)
515 && let Some(merged_hash) =
516 try_text_merge(store, b.object_hash, o.object_hash, t.object_hash)?
517 {
518 add_entry(merged, min_name, o.mode, merged_hash);
522 } else {
523 conflicts.push(Conflict {
527 path: join_path(prefix, min_name),
528 kind: ConflictKind::ModifyModify,
529 base_hash: Some(b.object_hash),
530 ours_hash: Some(o.object_hash),
531 theirs_hash: Some(t.object_hash),
532 ours_mode: Some(o.mode),
533 theirs_mode: Some(t.mode),
534 });
535 add_entry(merged, min_name, o.mode, o.object_hash);
537 }
538 }
539 (false, true, false) => {
540 let o = ours_entry.unwrap();
541 add_entry(merged, min_name, o.mode, o.object_hash);
542 }
543 (false, false, true) => {
544 let t = theirs_entry.unwrap();
545 add_entry(merged, min_name, t.mode, t.object_hash);
546 }
547 (false, true, true) => {
548 let o = ours_entry.unwrap();
549 let t = theirs_entry.unwrap();
550 if hash_and_mode_eq(o, t) {
551 add_entry(merged, min_name, o.mode, o.object_hash);
552 } else if o.mode == EntryMode::Tree && t.mode == EntryMode::Tree {
553 recurse_subtree_merge(
554 store,
555 None,
556 Some(o.object_hash),
557 Some(t.object_hash),
558 min_name,
559 prefix,
560 merged,
561 conflicts,
562 depth,
563 )?;
564 } else {
565 conflicts.push(Conflict {
566 path: join_path(prefix, min_name),
567 kind: ConflictKind::AddAdd,
568 base_hash: None,
569 ours_hash: Some(o.object_hash),
570 theirs_hash: Some(t.object_hash),
571 ours_mode: Some(o.mode),
572 theirs_mode: Some(t.mode),
573 });
574 add_entry(merged, min_name, o.mode, o.object_hash);
575 }
576 }
577 (true, true, false) => {
578 let b = base_entry.unwrap();
579 let o = ours_entry.unwrap();
580 if hash_and_mode_eq(b, o) {
581 } else {
583 conflicts.push(Conflict {
584 path: join_path(prefix, min_name),
585 kind: ConflictKind::DeleteModify,
586 base_hash: Some(b.object_hash),
587 ours_hash: Some(o.object_hash),
588 theirs_hash: None,
589 ours_mode: Some(o.mode),
590 theirs_mode: None,
591 });
592 add_entry(merged, min_name, o.mode, o.object_hash);
593 }
594 }
595 (true, false, true) => {
596 let b = base_entry.unwrap();
597 let t = theirs_entry.unwrap();
598 if hash_and_mode_eq(b, t) {
599 } else {
601 conflicts.push(Conflict {
602 path: join_path(prefix, min_name),
603 kind: ConflictKind::DeleteModify,
604 base_hash: Some(b.object_hash),
605 ours_hash: None,
606 theirs_hash: Some(t.object_hash),
607 ours_mode: None,
608 theirs_mode: Some(t.mode),
609 });
610 add_entry(merged, min_name, t.mode, t.object_hash);
611 }
612 }
613 (true, false, false) => {
614 }
616 (false, false, false) => {
617 break;
620 }
621 }
622 }
623 Ok(())
624}
625
626#[cfg(test)]
631#[allow(clippy::many_single_char_names)] mod tests {
633 use super::*;
634 use crate::object::{Blob, Commit, EntryMode, Identity, Object, Tree, TreeEntry};
635 use crate::serialize;
636 use tempfile::TempDir;
637
638 fn store() -> (TempDir, ObjectStore) {
639 let d = TempDir::new().unwrap();
640 let s = ObjectStore::init(d.path()).unwrap();
641 (d, s)
642 }
643 fn put_blob(s: &ObjectStore, data: &[u8]) -> Hash {
644 let bytes = serialize::serialize(&Object::Blob(Blob {
645 data: data.to_vec(),
646 }))
647 .unwrap();
648 s.write(&bytes).unwrap()
649 }
650 fn make_tree(s: &ObjectStore, entries: Vec<TreeEntry>) -> Hash {
651 let bytes = serialize::serialize(&Object::Tree(Tree { entries })).unwrap();
652 s.write(&bytes).unwrap()
653 }
654 fn entry(name: &[u8], mode: EntryMode, h: Hash) -> TreeEntry {
655 TreeEntry {
656 name: name.to_vec(),
657 mode,
658 object_hash: h,
659 }
660 }
661 fn make_commit(s: &ObjectStore, tree: Hash, parents: &[Hash], message: &str) -> Hash {
662 let c = Commit {
663 tree_hash: tree,
664 parents: parents.to_vec(),
665 author: Identity::ed25519([0; 32]),
666 signer: [0; 32],
667 message: message.as_bytes().to_vec(),
668 timestamp: message.len() as u64,
669 message_hash: [0; 32],
670 content_digest: [0; 32],
671 signature: [0; 64],
672 };
673 let bytes = serialize::serialize(&Object::Commit(c)).unwrap();
674 s.write(&bytes).unwrap()
675 }
676 fn tree_entries(s: &ObjectStore, h: Hash) -> Vec<TreeEntry> {
677 match s.read_object(&h).unwrap() {
678 Object::Tree(t) => t.entries,
679 other => panic!("expected tree, got {other}"),
680 }
681 }
682 fn find_entry<'a>(entries: &'a [TreeEntry], name: &[u8]) -> Option<&'a TreeEntry> {
683 entries.iter().find(|e| e.name == name)
684 }
685
686 #[test]
687 fn merge_identical_trees() {
688 let (_d, s) = store();
689 let blob_a = put_blob(&s, b"aaa");
690 let tree = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, blob_a)]);
691 let r = merge_trees(&s, Some(tree), Some(tree), Some(tree)).unwrap();
692 assert!(!r.has_conflicts());
693 assert_eq!(r.tree_hash, tree);
694 }
695
696 #[test]
697 fn merge_ours_adds_file() {
698 let (_d, s) = store();
699 let a = put_blob(&s, b"aaa");
700 let b = put_blob(&s, b"bbb");
701 let base = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, a)]);
702 let ours = make_tree(
703 &s,
704 vec![
705 entry(b"a.txt", EntryMode::Blob, a),
706 entry(b"b.txt", EntryMode::Blob, b),
707 ],
708 );
709 let r = merge_trees(&s, Some(base), Some(ours), Some(base)).unwrap();
710 assert!(!r.has_conflicts());
711 let entries = tree_entries(&s, r.tree_hash);
712 assert_eq!(entries.len(), 2);
713 assert_eq!(find_entry(&entries, b"a.txt").unwrap().object_hash, a);
714 assert_eq!(find_entry(&entries, b"b.txt").unwrap().object_hash, b);
715 }
716
717 #[test]
718 fn merge_theirs_adds_file() {
719 let (_d, s) = store();
720 let a = put_blob(&s, b"aaa");
721 let c = put_blob(&s, b"ccc");
722 let base = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, a)]);
723 let theirs = make_tree(
724 &s,
725 vec![
726 entry(b"a.txt", EntryMode::Blob, a),
727 entry(b"c.txt", EntryMode::Blob, c),
728 ],
729 );
730 let r = merge_trees(&s, Some(base), Some(base), Some(theirs)).unwrap();
731 assert!(!r.has_conflicts());
732 let entries = tree_entries(&s, r.tree_hash);
733 assert_eq!(entries.len(), 2);
734 assert_eq!(find_entry(&entries, b"c.txt").unwrap().object_hash, c);
735 }
736
737 #[test]
738 fn merge_both_add_different_files() {
739 let (_d, s) = store();
740 let a = put_blob(&s, b"aaa");
741 let b = put_blob(&s, b"bbb");
742 let c = put_blob(&s, b"ccc");
743 let base = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, a)]);
744 let ours = make_tree(
745 &s,
746 vec![
747 entry(b"a.txt", EntryMode::Blob, a),
748 entry(b"b.txt", EntryMode::Blob, b),
749 ],
750 );
751 let theirs = make_tree(
752 &s,
753 vec![
754 entry(b"a.txt", EntryMode::Blob, a),
755 entry(b"c.txt", EntryMode::Blob, c),
756 ],
757 );
758 let r = merge_trees(&s, Some(base), Some(ours), Some(theirs)).unwrap();
759 assert!(!r.has_conflicts());
760 assert_eq!(tree_entries(&s, r.tree_hash).len(), 3);
761 }
762
763 #[test]
764 fn merge_both_add_same_file_same_content() {
765 let (_d, s) = store();
766 let a = put_blob(&s, b"aaa");
767 let b = put_blob(&s, b"bbb");
768 let base = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, a)]);
769 let twin = make_tree(
770 &s,
771 vec![
772 entry(b"a.txt", EntryMode::Blob, a),
773 entry(b"b.txt", EntryMode::Blob, b),
774 ],
775 );
776 let r = merge_trees(&s, Some(base), Some(twin), Some(twin)).unwrap();
777 assert!(!r.has_conflicts());
778 assert_eq!(
779 find_entry(&tree_entries(&s, r.tree_hash), b"b.txt")
780 .unwrap()
781 .object_hash,
782 b
783 );
784 }
785
786 #[test]
787 fn merge_both_add_same_file_different_content() {
788 let (_d, s) = store();
789 let a = put_blob(&s, b"aaa");
790 let b1 = put_blob(&s, b"bbb-ours");
791 let b2 = put_blob(&s, b"bbb-theirs");
792 let base = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, a)]);
793 let ours = make_tree(
794 &s,
795 vec![
796 entry(b"a.txt", EntryMode::Blob, a),
797 entry(b"b.txt", EntryMode::Blob, b1),
798 ],
799 );
800 let theirs = make_tree(
801 &s,
802 vec![
803 entry(b"a.txt", EntryMode::Blob, a),
804 entry(b"b.txt", EntryMode::Blob, b2),
805 ],
806 );
807 let r = merge_trees(&s, Some(base), Some(ours), Some(theirs)).unwrap();
808 assert!(r.has_conflicts());
809 assert_eq!(r.conflicts.len(), 1);
810 let c = &r.conflicts[0];
811 assert_eq!(c.path, "b.txt");
812 assert_eq!(c.kind, ConflictKind::AddAdd);
813 assert_eq!(c.base_hash, None);
814 assert_eq!(c.ours_hash, Some(b1));
815 assert_eq!(c.theirs_hash, Some(b2));
816 }
817
818 #[test]
819 fn merge_ours_modifies() {
820 let (_d, s) = store();
821 let v1 = put_blob(&s, b"v1");
822 let v2 = put_blob(&s, b"v2");
823 let base = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, v1)]);
824 let ours = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, v2)]);
825 let r = merge_trees(&s, Some(base), Some(ours), Some(base)).unwrap();
826 assert!(!r.has_conflicts());
827 assert_eq!(
828 find_entry(&tree_entries(&s, r.tree_hash), b"a.txt")
829 .unwrap()
830 .object_hash,
831 v2
832 );
833 }
834
835 #[test]
836 fn merge_theirs_modifies() {
837 let (_d, s) = store();
838 let v1 = put_blob(&s, b"v1");
839 let v2 = put_blob(&s, b"v2");
840 let base = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, v1)]);
841 let theirs = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, v2)]);
842 let r = merge_trees(&s, Some(base), Some(base), Some(theirs)).unwrap();
843 assert!(!r.has_conflicts());
844 assert_eq!(
845 find_entry(&tree_entries(&s, r.tree_hash), b"a.txt")
846 .unwrap()
847 .object_hash,
848 v2
849 );
850 }
851
852 #[test]
853 fn merge_both_modify_same_way() {
854 let (_d, s) = store();
855 let v1 = put_blob(&s, b"v1");
856 let v2 = put_blob(&s, b"v2");
857 let base = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, v1)]);
858 let modified = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, v2)]);
859 let r = merge_trees(&s, Some(base), Some(modified), Some(modified)).unwrap();
860 assert!(!r.has_conflicts());
861 }
862
863 #[test]
864 fn merge_both_modify_differently() {
865 let (_d, s) = store();
866 let v1 = put_blob(&s, b"v1");
867 let v2 = put_blob(&s, b"v2-ours");
868 let v3 = put_blob(&s, b"v3-theirs");
869 let base = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, v1)]);
870 let ours = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, v2)]);
871 let theirs = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, v3)]);
872 let r = merge_trees(&s, Some(base), Some(ours), Some(theirs)).unwrap();
873 assert!(r.has_conflicts());
874 let c = &r.conflicts[0];
875 assert_eq!(c.path, "a.txt");
876 assert_eq!(c.kind, ConflictKind::ModifyModify);
877 assert_eq!(c.base_hash, Some(v1));
878 assert_eq!(c.ours_hash, Some(v2));
879 assert_eq!(c.theirs_hash, Some(v3));
880 }
881
882 #[test]
883 fn merge_ours_deletes() {
884 let (_d, s) = store();
885 let a = put_blob(&s, b"aaa");
886 let b = put_blob(&s, b"bbb");
887 let base = make_tree(
888 &s,
889 vec![
890 entry(b"a.txt", EntryMode::Blob, a),
891 entry(b"b.txt", EntryMode::Blob, b),
892 ],
893 );
894 let ours = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, a)]);
895 let r = merge_trees(&s, Some(base), Some(ours), Some(base)).unwrap();
896 assert!(!r.has_conflicts());
897 assert_eq!(tree_entries(&s, r.tree_hash).len(), 1);
898 }
899
900 #[test]
901 fn merge_theirs_deletes() {
902 let (_d, s) = store();
903 let a = put_blob(&s, b"aaa");
904 let b = put_blob(&s, b"bbb");
905 let base = make_tree(
906 &s,
907 vec![
908 entry(b"a.txt", EntryMode::Blob, a),
909 entry(b"b.txt", EntryMode::Blob, b),
910 ],
911 );
912 let theirs = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, a)]);
913 let r = merge_trees(&s, Some(base), Some(base), Some(theirs)).unwrap();
914 assert!(!r.has_conflicts());
915 assert_eq!(tree_entries(&s, r.tree_hash).len(), 1);
916 }
917
918 #[test]
919 fn merge_both_delete() {
920 let (_d, s) = store();
921 let a = put_blob(&s, b"aaa");
922 let b = put_blob(&s, b"bbb");
923 let base = make_tree(
924 &s,
925 vec![
926 entry(b"a.txt", EntryMode::Blob, a),
927 entry(b"b.txt", EntryMode::Blob, b),
928 ],
929 );
930 let both = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, a)]);
931 let r = merge_trees(&s, Some(base), Some(both), Some(both)).unwrap();
932 assert!(!r.has_conflicts());
933 assert_eq!(tree_entries(&s, r.tree_hash).len(), 1);
934 }
935
936 #[test]
937 fn merge_delete_vs_modify() {
938 let (_d, s) = store();
939 let a = put_blob(&s, b"aaa");
940 let b1 = put_blob(&s, b"bbb-v1");
941 let b2 = put_blob(&s, b"bbb-v2");
942 let base = make_tree(
943 &s,
944 vec![
945 entry(b"a.txt", EntryMode::Blob, a),
946 entry(b"b.txt", EntryMode::Blob, b1),
947 ],
948 );
949 let ours = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, a)]);
950 let theirs = make_tree(
951 &s,
952 vec![
953 entry(b"a.txt", EntryMode::Blob, a),
954 entry(b"b.txt", EntryMode::Blob, b2),
955 ],
956 );
957 let r = merge_trees(&s, Some(base), Some(ours), Some(theirs)).unwrap();
958 assert!(r.has_conflicts());
959 let c = &r.conflicts[0];
960 assert_eq!(c.path, "b.txt");
961 assert_eq!(c.kind, ConflictKind::DeleteModify);
962 assert_eq!(c.base_hash, Some(b1));
963 assert_eq!(c.ours_hash, None);
964 assert_eq!(c.theirs_hash, Some(b2));
965 }
966
967 #[test]
968 fn merge_nested_tree_changes() {
969 let (_d, s) = store();
970 let main_v1 = put_blob(&s, b"fn main() {}");
971 let main_v2 = put_blob(&s, b"fn main() { run(); }");
972 let util = put_blob(&s, b"fn util() {}");
973 let base_sub = make_tree(&s, vec![entry(b"main.rs", EntryMode::Blob, main_v1)]);
974 let base = make_tree(&s, vec![entry(b"src", EntryMode::Tree, base_sub)]);
975 let ours_sub = make_tree(&s, vec![entry(b"main.rs", EntryMode::Blob, main_v2)]);
976 let ours = make_tree(&s, vec![entry(b"src", EntryMode::Tree, ours_sub)]);
977 let theirs_sub = make_tree(
978 &s,
979 vec![
980 entry(b"main.rs", EntryMode::Blob, main_v1),
981 entry(b"util.rs", EntryMode::Blob, util),
982 ],
983 );
984 let theirs = make_tree(&s, vec![entry(b"src", EntryMode::Tree, theirs_sub)]);
985 let r = merge_trees(&s, Some(base), Some(ours), Some(theirs)).unwrap();
986 assert!(!r.has_conflicts());
987 let root = tree_entries(&s, r.tree_hash);
988 assert_eq!(root.len(), 1);
989 let src = tree_entries(&s, root[0].object_hash);
990 assert_eq!(src.len(), 2);
991 assert_eq!(find_entry(&src, b"main.rs").unwrap().object_hash, main_v2);
992 assert_eq!(find_entry(&src, b"util.rs").unwrap().object_hash, util);
993 }
994
995 #[test]
996 fn merge_nested_conflict_path_is_full() {
997 let (_d, s) = store();
998 let v1 = put_blob(&s, b"original");
999 let v2 = put_blob(&s, b"ours-change");
1000 let v3 = put_blob(&s, b"theirs-change");
1001 let base_sub = make_tree(&s, vec![entry(b"main.rs", EntryMode::Blob, v1)]);
1002 let base = make_tree(&s, vec![entry(b"src", EntryMode::Tree, base_sub)]);
1003 let ours_sub = make_tree(&s, vec![entry(b"main.rs", EntryMode::Blob, v2)]);
1004 let ours = make_tree(&s, vec![entry(b"src", EntryMode::Tree, ours_sub)]);
1005 let theirs_sub = make_tree(&s, vec![entry(b"main.rs", EntryMode::Blob, v3)]);
1006 let theirs = make_tree(&s, vec![entry(b"src", EntryMode::Tree, theirs_sub)]);
1007 let r = merge_trees(&s, Some(base), Some(ours), Some(theirs)).unwrap();
1008 assert!(r.has_conflicts());
1009 assert_eq!(r.conflicts[0].path, "src/main.rs");
1010 assert_eq!(r.conflicts[0].kind, ConflictKind::ModifyModify);
1011 assert_eq!(r.conflicts[0].base_hash, Some(v1));
1012 assert_eq!(r.conflicts[0].ours_hash, Some(v2));
1013 assert_eq!(r.conflicts[0].theirs_hash, Some(v3));
1014 }
1015
1016 #[test]
1017 fn merge_empty_base() {
1018 let (_d, s) = store();
1019 let a = put_blob(&s, b"aaa");
1020 let b = put_blob(&s, b"bbb");
1021 let ours = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, a)]);
1022 let theirs = make_tree(&s, vec![entry(b"b.txt", EntryMode::Blob, b)]);
1023 let r = merge_trees(&s, None, Some(ours), Some(theirs)).unwrap();
1024 assert!(!r.has_conflicts());
1025 assert_eq!(tree_entries(&s, r.tree_hash).len(), 2);
1026 }
1027
1028 #[test]
1031 fn find_merge_base_linear() {
1032 let (_d, s) = store();
1033 let empty = make_tree(&s, vec![]);
1034 let a = make_commit(&s, empty, &[], "A");
1035 let b = make_commit(&s, empty, &[a], "B");
1036 let c = make_commit(&s, empty, &[b], "C");
1037 let d = make_commit(&s, empty, &[b], "D");
1038 assert_eq!(find_merge_base(&s, c, d).unwrap(), Some(b));
1039 }
1040
1041 #[test]
1042 fn find_merge_base_root() {
1043 let (_d, s) = store();
1044 let empty = make_tree(&s, vec![]);
1045 let a = make_commit(&s, empty, &[], "A");
1046 let b = make_commit(&s, empty, &[a], "B");
1047 let c = make_commit(&s, empty, &[a], "C");
1048 assert_eq!(find_merge_base(&s, b, c).unwrap(), Some(a));
1049 }
1050
1051 #[test]
1052 fn find_merge_base_no_common_ancestor() {
1053 let (_d, s) = store();
1054 let empty = make_tree(&s, vec![]);
1055 let x = make_commit(&s, empty, &[], "X");
1056 let y = make_commit(&s, empty, &[], "Y");
1057 assert_eq!(find_merge_base(&s, x, y).unwrap(), None);
1058 }
1059
1060 #[test]
1061 fn find_merge_base_same_commit() {
1062 let (_d, s) = store();
1063 let empty = make_tree(&s, vec![]);
1064 let a = make_commit(&s, empty, &[], "A");
1065 assert_eq!(find_merge_base(&s, a, a).unwrap(), Some(a));
1066 }
1067
1068 #[test]
1069 fn find_merge_base_walks_non_first_parents() {
1070 let (_d, s) = store();
1071 let empty = make_tree(&s, vec![]);
1072 let root = make_commit(&s, empty, &[], "root");
1073 let left = make_commit(&s, empty, &[root], "left");
1074 let right = make_commit(&s, empty, &[root], "right");
1075 let m = make_commit(&s, empty, &[left, right], "merge");
1076 let tip = make_commit(&s, empty, &[m], "tip");
1077 assert_eq!(find_merge_base(&s, tip, right).unwrap(), Some(right));
1078 }
1079
1080 #[test]
1081 fn is_ancestor_walks_merge_parents() {
1082 let (_d, s) = store();
1083 let empty = make_tree(&s, vec![]);
1084 let root = make_commit(&s, empty, &[], "root");
1085 let left = make_commit(&s, empty, &[root], "left");
1086 let right = make_commit(&s, empty, &[root], "right");
1087 let m = make_commit(&s, empty, &[left, right], "merge");
1088
1089 assert!(is_ancestor(&s, root, m).unwrap());
1090 assert!(is_ancestor(&s, right, m).unwrap());
1091 assert!(!is_ancestor(&s, m, right).unwrap());
1092 }
1093
1094 #[test]
1095 fn merge_both_modify_disjoint_lines_auto_merges() {
1096 let (_d, s) = store();
1097 let base_b = put_blob(&s, b"a\nb\nc\nd\ne\n");
1098 let ours_b = put_blob(&s, b"A\nb\nc\nd\ne\n"); let theirs_b = put_blob(&s, b"a\nb\nc\nd\nE\n"); let base = make_tree(&s, vec![entry(b"f.txt", EntryMode::Blob, base_b)]);
1101 let ours = make_tree(&s, vec![entry(b"f.txt", EntryMode::Blob, ours_b)]);
1102 let theirs = make_tree(&s, vec![entry(b"f.txt", EntryMode::Blob, theirs_b)]);
1103 let r = merge_trees(&s, Some(base), Some(ours), Some(theirs)).unwrap();
1104 assert!(
1105 !r.has_conflicts(),
1106 "disjoint line edits should auto-merge (#298), got {:?}",
1107 r.conflicts
1108 );
1109 let f = find_entry(&tree_entries(&s, r.tree_hash), b"f.txt")
1110 .unwrap()
1111 .object_hash;
1112 let merged = match s.read_object(&f).unwrap() {
1113 Object::Blob(b) => b.data,
1114 other => panic!("merged f.txt not a blob: {other:?}"),
1115 };
1116 assert_eq!(merged, b"A\nb\nc\nd\nE\n");
1117 }
1118
1119 #[test]
1120 fn merge_both_modify_same_line_still_conflicts() {
1121 let (_d, s) = store();
1122 let base_b = put_blob(&s, b"a\nb\nc\n");
1123 let ours_b = put_blob(&s, b"a\nOURS\nc\n"); let theirs_b = put_blob(&s, b"a\nTHEIRS\nc\n"); let base = make_tree(&s, vec![entry(b"f.txt", EntryMode::Blob, base_b)]);
1126 let ours = make_tree(&s, vec![entry(b"f.txt", EntryMode::Blob, ours_b)]);
1127 let theirs = make_tree(&s, vec![entry(b"f.txt", EntryMode::Blob, theirs_b)]);
1128 let r = merge_trees(&s, Some(base), Some(ours), Some(theirs)).unwrap();
1129 assert!(r.has_conflicts(), "same-line edits must still conflict");
1130 assert_eq!(r.conflicts[0].kind, ConflictKind::ModifyModify);
1131 }
1132}