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 (base_entry, ours_entry, theirs_entry) {
440 (Some(b), Some(o), Some(t)) => {
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 (None, Some(o), None) => {
540 add_entry(merged, min_name, o.mode, o.object_hash);
541 }
542 (None, None, Some(t)) => {
543 add_entry(merged, min_name, t.mode, t.object_hash);
544 }
545 (None, Some(o), Some(t)) => {
546 if hash_and_mode_eq(o, t) {
547 add_entry(merged, min_name, o.mode, o.object_hash);
548 } else if o.mode == EntryMode::Tree && t.mode == EntryMode::Tree {
549 recurse_subtree_merge(
550 store,
551 None,
552 Some(o.object_hash),
553 Some(t.object_hash),
554 min_name,
555 prefix,
556 merged,
557 conflicts,
558 depth,
559 )?;
560 } else {
561 conflicts.push(Conflict {
562 path: join_path(prefix, min_name),
563 kind: ConflictKind::AddAdd,
564 base_hash: None,
565 ours_hash: Some(o.object_hash),
566 theirs_hash: Some(t.object_hash),
567 ours_mode: Some(o.mode),
568 theirs_mode: Some(t.mode),
569 });
570 add_entry(merged, min_name, o.mode, o.object_hash);
571 }
572 }
573 (Some(b), Some(o), None) => {
574 if hash_and_mode_eq(b, o) {
575 } else {
577 conflicts.push(Conflict {
578 path: join_path(prefix, min_name),
579 kind: ConflictKind::DeleteModify,
580 base_hash: Some(b.object_hash),
581 ours_hash: Some(o.object_hash),
582 theirs_hash: None,
583 ours_mode: Some(o.mode),
584 theirs_mode: None,
585 });
586 add_entry(merged, min_name, o.mode, o.object_hash);
587 }
588 }
589 (Some(b), None, Some(t)) => {
590 if hash_and_mode_eq(b, t) {
591 } else {
593 conflicts.push(Conflict {
594 path: join_path(prefix, min_name),
595 kind: ConflictKind::DeleteModify,
596 base_hash: Some(b.object_hash),
597 ours_hash: None,
598 theirs_hash: Some(t.object_hash),
599 ours_mode: None,
600 theirs_mode: Some(t.mode),
601 });
602 add_entry(merged, min_name, t.mode, t.object_hash);
603 }
604 }
605 (Some(_b), None, None) => {
606 }
608 (None, None, None) => {
609 break;
612 }
613 }
614 }
615 Ok(())
616}
617
618#[cfg(test)]
623#[allow(clippy::many_single_char_names)] mod tests {
625 use super::*;
626 use crate::object::{Blob, Commit, EntryMode, Identity, Object, Tree, TreeEntry};
627 use crate::serialize;
628 use tempfile::TempDir;
629
630 fn store() -> (TempDir, ObjectStore) {
631 let d = TempDir::new().unwrap();
632 let s = ObjectStore::init(&crate::layout::RepoLayout::single(d.path())).unwrap();
633 (d, s)
634 }
635 fn put_blob(s: &ObjectStore, data: &[u8]) -> Hash {
636 let bytes = serialize::serialize(&Object::Blob(Blob {
637 data: data.to_vec(),
638 }))
639 .unwrap();
640 s.write(&bytes).unwrap()
641 }
642 fn make_tree(s: &ObjectStore, entries: Vec<TreeEntry>) -> Hash {
643 let bytes = serialize::serialize(&Object::Tree(Tree { entries })).unwrap();
644 s.write(&bytes).unwrap()
645 }
646 fn entry(name: &[u8], mode: EntryMode, h: Hash) -> TreeEntry {
647 TreeEntry {
648 name: name.to_vec(),
649 mode,
650 object_hash: h,
651 }
652 }
653 fn make_commit(s: &ObjectStore, tree: Hash, parents: &[Hash], message: &str) -> Hash {
654 let c = Commit {
655 tree_hash: tree,
656 parents: parents.to_vec(),
657 author: Identity::ed25519([0; 32]),
658 signer: [0; 32],
659 message: message.as_bytes().to_vec(),
660 timestamp: message.len() as u64,
661 message_hash: [0; 32],
662 content_digest: [0; 32],
663 signature: [0; 64],
664 };
665 let bytes = serialize::serialize(&Object::Commit(c)).unwrap();
666 s.write(&bytes).unwrap()
667 }
668 fn tree_entries(s: &ObjectStore, h: Hash) -> Vec<TreeEntry> {
669 match s.read_object(&h).unwrap() {
670 Object::Tree(t) => t.entries,
671 other => panic!("expected tree, got {other}"),
672 }
673 }
674 fn find_entry<'a>(entries: &'a [TreeEntry], name: &[u8]) -> Option<&'a TreeEntry> {
675 entries.iter().find(|e| e.name == name)
676 }
677
678 #[test]
679 fn merge_identical_trees() {
680 let (_d, s) = store();
681 let blob_a = put_blob(&s, b"aaa");
682 let tree = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, blob_a)]);
683 let r = merge_trees(&s, Some(tree), Some(tree), Some(tree)).unwrap();
684 assert!(!r.has_conflicts());
685 assert_eq!(r.tree_hash, tree);
686 }
687
688 #[test]
689 fn merge_ours_adds_file() {
690 let (_d, s) = store();
691 let a = put_blob(&s, b"aaa");
692 let b = put_blob(&s, b"bbb");
693 let base = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, a)]);
694 let ours = make_tree(
695 &s,
696 vec![
697 entry(b"a.txt", EntryMode::Blob, a),
698 entry(b"b.txt", EntryMode::Blob, b),
699 ],
700 );
701 let r = merge_trees(&s, Some(base), Some(ours), Some(base)).unwrap();
702 assert!(!r.has_conflicts());
703 let entries = tree_entries(&s, r.tree_hash);
704 assert_eq!(entries.len(), 2);
705 assert_eq!(find_entry(&entries, b"a.txt").unwrap().object_hash, a);
706 assert_eq!(find_entry(&entries, b"b.txt").unwrap().object_hash, b);
707 }
708
709 #[test]
710 fn merge_theirs_adds_file() {
711 let (_d, s) = store();
712 let a = put_blob(&s, b"aaa");
713 let c = put_blob(&s, b"ccc");
714 let base = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, a)]);
715 let theirs = make_tree(
716 &s,
717 vec![
718 entry(b"a.txt", EntryMode::Blob, a),
719 entry(b"c.txt", EntryMode::Blob, c),
720 ],
721 );
722 let r = merge_trees(&s, Some(base), Some(base), Some(theirs)).unwrap();
723 assert!(!r.has_conflicts());
724 let entries = tree_entries(&s, r.tree_hash);
725 assert_eq!(entries.len(), 2);
726 assert_eq!(find_entry(&entries, b"c.txt").unwrap().object_hash, c);
727 }
728
729 #[test]
730 fn merge_both_add_different_files() {
731 let (_d, s) = store();
732 let a = put_blob(&s, b"aaa");
733 let b = put_blob(&s, b"bbb");
734 let c = put_blob(&s, b"ccc");
735 let base = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, a)]);
736 let ours = make_tree(
737 &s,
738 vec![
739 entry(b"a.txt", EntryMode::Blob, a),
740 entry(b"b.txt", EntryMode::Blob, b),
741 ],
742 );
743 let theirs = make_tree(
744 &s,
745 vec![
746 entry(b"a.txt", EntryMode::Blob, a),
747 entry(b"c.txt", EntryMode::Blob, c),
748 ],
749 );
750 let r = merge_trees(&s, Some(base), Some(ours), Some(theirs)).unwrap();
751 assert!(!r.has_conflicts());
752 assert_eq!(tree_entries(&s, r.tree_hash).len(), 3);
753 }
754
755 #[test]
756 fn merge_both_add_same_file_same_content() {
757 let (_d, s) = store();
758 let a = put_blob(&s, b"aaa");
759 let b = put_blob(&s, b"bbb");
760 let base = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, a)]);
761 let twin = make_tree(
762 &s,
763 vec![
764 entry(b"a.txt", EntryMode::Blob, a),
765 entry(b"b.txt", EntryMode::Blob, b),
766 ],
767 );
768 let r = merge_trees(&s, Some(base), Some(twin), Some(twin)).unwrap();
769 assert!(!r.has_conflicts());
770 assert_eq!(
771 find_entry(&tree_entries(&s, r.tree_hash), b"b.txt")
772 .unwrap()
773 .object_hash,
774 b
775 );
776 }
777
778 #[test]
779 fn merge_both_add_same_file_different_content() {
780 let (_d, s) = store();
781 let a = put_blob(&s, b"aaa");
782 let b1 = put_blob(&s, b"bbb-ours");
783 let b2 = put_blob(&s, b"bbb-theirs");
784 let base = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, a)]);
785 let ours = make_tree(
786 &s,
787 vec![
788 entry(b"a.txt", EntryMode::Blob, a),
789 entry(b"b.txt", EntryMode::Blob, b1),
790 ],
791 );
792 let theirs = make_tree(
793 &s,
794 vec![
795 entry(b"a.txt", EntryMode::Blob, a),
796 entry(b"b.txt", EntryMode::Blob, b2),
797 ],
798 );
799 let r = merge_trees(&s, Some(base), Some(ours), Some(theirs)).unwrap();
800 assert!(r.has_conflicts());
801 assert_eq!(r.conflicts.len(), 1);
802 let c = &r.conflicts[0];
803 assert_eq!(c.path, "b.txt");
804 assert_eq!(c.kind, ConflictKind::AddAdd);
805 assert_eq!(c.base_hash, None);
806 assert_eq!(c.ours_hash, Some(b1));
807 assert_eq!(c.theirs_hash, Some(b2));
808 }
809
810 #[test]
811 fn merge_ours_modifies() {
812 let (_d, s) = store();
813 let v1 = put_blob(&s, b"v1");
814 let v2 = put_blob(&s, b"v2");
815 let base = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, v1)]);
816 let ours = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, v2)]);
817 let r = merge_trees(&s, Some(base), Some(ours), Some(base)).unwrap();
818 assert!(!r.has_conflicts());
819 assert_eq!(
820 find_entry(&tree_entries(&s, r.tree_hash), b"a.txt")
821 .unwrap()
822 .object_hash,
823 v2
824 );
825 }
826
827 #[test]
828 fn merge_theirs_modifies() {
829 let (_d, s) = store();
830 let v1 = put_blob(&s, b"v1");
831 let v2 = put_blob(&s, b"v2");
832 let base = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, v1)]);
833 let theirs = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, v2)]);
834 let r = merge_trees(&s, Some(base), Some(base), Some(theirs)).unwrap();
835 assert!(!r.has_conflicts());
836 assert_eq!(
837 find_entry(&tree_entries(&s, r.tree_hash), b"a.txt")
838 .unwrap()
839 .object_hash,
840 v2
841 );
842 }
843
844 #[test]
845 fn merge_both_modify_same_way() {
846 let (_d, s) = store();
847 let v1 = put_blob(&s, b"v1");
848 let v2 = put_blob(&s, b"v2");
849 let base = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, v1)]);
850 let modified = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, v2)]);
851 let r = merge_trees(&s, Some(base), Some(modified), Some(modified)).unwrap();
852 assert!(!r.has_conflicts());
853 }
854
855 #[test]
856 fn merge_both_modify_differently() {
857 let (_d, s) = store();
858 let v1 = put_blob(&s, b"v1");
859 let v2 = put_blob(&s, b"v2-ours");
860 let v3 = put_blob(&s, b"v3-theirs");
861 let base = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, v1)]);
862 let ours = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, v2)]);
863 let theirs = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, v3)]);
864 let r = merge_trees(&s, Some(base), Some(ours), Some(theirs)).unwrap();
865 assert!(r.has_conflicts());
866 let c = &r.conflicts[0];
867 assert_eq!(c.path, "a.txt");
868 assert_eq!(c.kind, ConflictKind::ModifyModify);
869 assert_eq!(c.base_hash, Some(v1));
870 assert_eq!(c.ours_hash, Some(v2));
871 assert_eq!(c.theirs_hash, Some(v3));
872 }
873
874 #[test]
875 fn merge_ours_deletes() {
876 let (_d, s) = store();
877 let a = put_blob(&s, b"aaa");
878 let b = put_blob(&s, b"bbb");
879 let base = make_tree(
880 &s,
881 vec![
882 entry(b"a.txt", EntryMode::Blob, a),
883 entry(b"b.txt", EntryMode::Blob, b),
884 ],
885 );
886 let ours = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, a)]);
887 let r = merge_trees(&s, Some(base), Some(ours), Some(base)).unwrap();
888 assert!(!r.has_conflicts());
889 assert_eq!(tree_entries(&s, r.tree_hash).len(), 1);
890 }
891
892 #[test]
893 fn merge_theirs_deletes() {
894 let (_d, s) = store();
895 let a = put_blob(&s, b"aaa");
896 let b = put_blob(&s, b"bbb");
897 let base = make_tree(
898 &s,
899 vec![
900 entry(b"a.txt", EntryMode::Blob, a),
901 entry(b"b.txt", EntryMode::Blob, b),
902 ],
903 );
904 let theirs = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, a)]);
905 let r = merge_trees(&s, Some(base), Some(base), Some(theirs)).unwrap();
906 assert!(!r.has_conflicts());
907 assert_eq!(tree_entries(&s, r.tree_hash).len(), 1);
908 }
909
910 #[test]
911 fn merge_both_delete() {
912 let (_d, s) = store();
913 let a = put_blob(&s, b"aaa");
914 let b = put_blob(&s, b"bbb");
915 let base = make_tree(
916 &s,
917 vec![
918 entry(b"a.txt", EntryMode::Blob, a),
919 entry(b"b.txt", EntryMode::Blob, b),
920 ],
921 );
922 let both = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, a)]);
923 let r = merge_trees(&s, Some(base), Some(both), Some(both)).unwrap();
924 assert!(!r.has_conflicts());
925 assert_eq!(tree_entries(&s, r.tree_hash).len(), 1);
926 }
927
928 #[test]
929 fn merge_delete_vs_modify() {
930 let (_d, s) = store();
931 let a = put_blob(&s, b"aaa");
932 let b1 = put_blob(&s, b"bbb-v1");
933 let b2 = put_blob(&s, b"bbb-v2");
934 let base = make_tree(
935 &s,
936 vec![
937 entry(b"a.txt", EntryMode::Blob, a),
938 entry(b"b.txt", EntryMode::Blob, b1),
939 ],
940 );
941 let ours = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, a)]);
942 let theirs = make_tree(
943 &s,
944 vec![
945 entry(b"a.txt", EntryMode::Blob, a),
946 entry(b"b.txt", EntryMode::Blob, b2),
947 ],
948 );
949 let r = merge_trees(&s, Some(base), Some(ours), Some(theirs)).unwrap();
950 assert!(r.has_conflicts());
951 let c = &r.conflicts[0];
952 assert_eq!(c.path, "b.txt");
953 assert_eq!(c.kind, ConflictKind::DeleteModify);
954 assert_eq!(c.base_hash, Some(b1));
955 assert_eq!(c.ours_hash, None);
956 assert_eq!(c.theirs_hash, Some(b2));
957 }
958
959 #[test]
960 fn merge_nested_tree_changes() {
961 let (_d, s) = store();
962 let main_v1 = put_blob(&s, b"fn main() {}");
963 let main_v2 = put_blob(&s, b"fn main() { run(); }");
964 let util = put_blob(&s, b"fn util() {}");
965 let base_sub = make_tree(&s, vec![entry(b"main.rs", EntryMode::Blob, main_v1)]);
966 let base = make_tree(&s, vec![entry(b"src", EntryMode::Tree, base_sub)]);
967 let ours_sub = make_tree(&s, vec![entry(b"main.rs", EntryMode::Blob, main_v2)]);
968 let ours = make_tree(&s, vec![entry(b"src", EntryMode::Tree, ours_sub)]);
969 let theirs_sub = make_tree(
970 &s,
971 vec![
972 entry(b"main.rs", EntryMode::Blob, main_v1),
973 entry(b"util.rs", EntryMode::Blob, util),
974 ],
975 );
976 let theirs = make_tree(&s, vec![entry(b"src", EntryMode::Tree, theirs_sub)]);
977 let r = merge_trees(&s, Some(base), Some(ours), Some(theirs)).unwrap();
978 assert!(!r.has_conflicts());
979 let root = tree_entries(&s, r.tree_hash);
980 assert_eq!(root.len(), 1);
981 let src = tree_entries(&s, root[0].object_hash);
982 assert_eq!(src.len(), 2);
983 assert_eq!(find_entry(&src, b"main.rs").unwrap().object_hash, main_v2);
984 assert_eq!(find_entry(&src, b"util.rs").unwrap().object_hash, util);
985 }
986
987 #[test]
988 fn merge_nested_conflict_path_is_full() {
989 let (_d, s) = store();
990 let v1 = put_blob(&s, b"original");
991 let v2 = put_blob(&s, b"ours-change");
992 let v3 = put_blob(&s, b"theirs-change");
993 let base_sub = make_tree(&s, vec![entry(b"main.rs", EntryMode::Blob, v1)]);
994 let base = make_tree(&s, vec![entry(b"src", EntryMode::Tree, base_sub)]);
995 let ours_sub = make_tree(&s, vec![entry(b"main.rs", EntryMode::Blob, v2)]);
996 let ours = make_tree(&s, vec![entry(b"src", EntryMode::Tree, ours_sub)]);
997 let theirs_sub = make_tree(&s, vec![entry(b"main.rs", EntryMode::Blob, v3)]);
998 let theirs = make_tree(&s, vec![entry(b"src", EntryMode::Tree, theirs_sub)]);
999 let r = merge_trees(&s, Some(base), Some(ours), Some(theirs)).unwrap();
1000 assert!(r.has_conflicts());
1001 assert_eq!(r.conflicts[0].path, "src/main.rs");
1002 assert_eq!(r.conflicts[0].kind, ConflictKind::ModifyModify);
1003 assert_eq!(r.conflicts[0].base_hash, Some(v1));
1004 assert_eq!(r.conflicts[0].ours_hash, Some(v2));
1005 assert_eq!(r.conflicts[0].theirs_hash, Some(v3));
1006 }
1007
1008 #[test]
1009 fn merge_empty_base() {
1010 let (_d, s) = store();
1011 let a = put_blob(&s, b"aaa");
1012 let b = put_blob(&s, b"bbb");
1013 let ours = make_tree(&s, vec![entry(b"a.txt", EntryMode::Blob, a)]);
1014 let theirs = make_tree(&s, vec![entry(b"b.txt", EntryMode::Blob, b)]);
1015 let r = merge_trees(&s, None, Some(ours), Some(theirs)).unwrap();
1016 assert!(!r.has_conflicts());
1017 assert_eq!(tree_entries(&s, r.tree_hash).len(), 2);
1018 }
1019
1020 #[test]
1023 fn find_merge_base_linear() {
1024 let (_d, s) = store();
1025 let empty = make_tree(&s, vec![]);
1026 let a = make_commit(&s, empty, &[], "A");
1027 let b = make_commit(&s, empty, &[a], "B");
1028 let c = make_commit(&s, empty, &[b], "C");
1029 let d = make_commit(&s, empty, &[b], "D");
1030 assert_eq!(find_merge_base(&s, c, d).unwrap(), Some(b));
1031 }
1032
1033 #[test]
1034 fn find_merge_base_root() {
1035 let (_d, s) = store();
1036 let empty = make_tree(&s, vec![]);
1037 let a = make_commit(&s, empty, &[], "A");
1038 let b = make_commit(&s, empty, &[a], "B");
1039 let c = make_commit(&s, empty, &[a], "C");
1040 assert_eq!(find_merge_base(&s, b, c).unwrap(), Some(a));
1041 }
1042
1043 #[test]
1044 fn find_merge_base_no_common_ancestor() {
1045 let (_d, s) = store();
1046 let empty = make_tree(&s, vec![]);
1047 let x = make_commit(&s, empty, &[], "X");
1048 let y = make_commit(&s, empty, &[], "Y");
1049 assert_eq!(find_merge_base(&s, x, y).unwrap(), None);
1050 }
1051
1052 #[test]
1053 fn find_merge_base_same_commit() {
1054 let (_d, s) = store();
1055 let empty = make_tree(&s, vec![]);
1056 let a = make_commit(&s, empty, &[], "A");
1057 assert_eq!(find_merge_base(&s, a, a).unwrap(), Some(a));
1058 }
1059
1060 #[test]
1061 fn find_merge_base_walks_non_first_parents() {
1062 let (_d, s) = store();
1063 let empty = make_tree(&s, vec![]);
1064 let root = make_commit(&s, empty, &[], "root");
1065 let left = make_commit(&s, empty, &[root], "left");
1066 let right = make_commit(&s, empty, &[root], "right");
1067 let m = make_commit(&s, empty, &[left, right], "merge");
1068 let tip = make_commit(&s, empty, &[m], "tip");
1069 assert_eq!(find_merge_base(&s, tip, right).unwrap(), Some(right));
1070 }
1071
1072 #[test]
1073 fn is_ancestor_walks_merge_parents() {
1074 let (_d, s) = store();
1075 let empty = make_tree(&s, vec![]);
1076 let root = make_commit(&s, empty, &[], "root");
1077 let left = make_commit(&s, empty, &[root], "left");
1078 let right = make_commit(&s, empty, &[root], "right");
1079 let m = make_commit(&s, empty, &[left, right], "merge");
1080
1081 assert!(is_ancestor(&s, root, m).unwrap());
1082 assert!(is_ancestor(&s, right, m).unwrap());
1083 assert!(!is_ancestor(&s, m, right).unwrap());
1084 }
1085
1086 #[test]
1087 fn merge_both_modify_disjoint_lines_auto_merges() {
1088 let (_d, s) = store();
1089 let base_b = put_blob(&s, b"a\nb\nc\nd\ne\n");
1090 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)]);
1093 let ours = make_tree(&s, vec![entry(b"f.txt", EntryMode::Blob, ours_b)]);
1094 let theirs = make_tree(&s, vec![entry(b"f.txt", EntryMode::Blob, theirs_b)]);
1095 let r = merge_trees(&s, Some(base), Some(ours), Some(theirs)).unwrap();
1096 assert!(
1097 !r.has_conflicts(),
1098 "disjoint line edits should auto-merge (#298), got {:?}",
1099 r.conflicts
1100 );
1101 let f = find_entry(&tree_entries(&s, r.tree_hash), b"f.txt")
1102 .unwrap()
1103 .object_hash;
1104 let merged = match s.read_object(&f).unwrap() {
1105 Object::Blob(b) => b.data,
1106 other => panic!("merged f.txt not a blob: {other:?}"),
1107 };
1108 assert_eq!(merged, b"A\nb\nc\nd\nE\n");
1109 }
1110
1111 #[test]
1112 fn merge_both_modify_same_line_still_conflicts() {
1113 let (_d, s) = store();
1114 let base_b = put_blob(&s, b"a\nb\nc\n");
1115 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)]);
1118 let ours = make_tree(&s, vec![entry(b"f.txt", EntryMode::Blob, ours_b)]);
1119 let theirs = make_tree(&s, vec![entry(b"f.txt", EntryMode::Blob, theirs_b)]);
1120 let r = merge_trees(&s, Some(base), Some(ours), Some(theirs)).unwrap();
1121 assert!(r.has_conflicts(), "same-line edits must still conflict");
1122 assert_eq!(r.conflicts[0].kind, ConflictKind::ModifyModify);
1123 }
1124}