Skip to main content

xml_3dm/merge/
mod.rs

1//! 3-way merge algorithm.
2//!
3//! This module implements the core 3-way merge algorithm that combines
4//! two branch trees relative to a common base tree. The algorithm handles
5//! insert, delete, update, move, and copy operations on subtrees.
6//!
7//! # Algorithm Overview
8//!
9//! The merge proceeds recursively through the matched trees:
10//! 1. Create MergeLists for each branch's children
11//! 2. Remove deleted/moved nodes and resolve conflicts
12//! 3. Merge the lists into MergePairs
13//! 4. For each pair, merge node content and recurse for children
14
15mod conflict_log;
16mod edit_log;
17mod merge_list;
18mod merge_pair;
19
20pub use conflict_log::{ConflictLog, ConflictType};
21pub use edit_log::{EditLog, EditType};
22pub use merge_list::{MergeEntry, MergeList};
23pub use merge_pair::{MergePair, MergePairList};
24
25use std::io::Write;
26
27use crate::matching::TriMatching;
28use crate::node::base::BaseNode;
29use crate::node::branch::BranchNode;
30use crate::node::{new_node_ref, MatchType, NodeInner, NodeRef, XmlContent, XmlElement};
31use crate::xml::XmlPrinter;
32
33/// Operation codes for merge list processing.
34#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
35pub enum Operation {
36    /// No operation - node unchanged.
37    Nop,
38    /// Move inside child list.
39    MoveI,
40    /// Move far (to different parent).
41    MoveF,
42    /// Node deleted.
43    Delete,
44}
45
46/// The main merge struct that orchestrates the 3-way merge.
47pub struct Merge {
48    /// The tri-matching with base, left, and right trees.
49    matching: TriMatching,
50    /// Log of conflicts encountered during merge.
51    pub conflict_log: ConflictLog,
52    /// Log of edit operations performed.
53    pub edit_log: EditLog,
54}
55
56impl Merge {
57    /// Creates a new Merge from a TriMatching.
58    pub fn new(matching: TriMatching) -> Self {
59        Merge {
60            matching,
61            conflict_log: ConflictLog::new(),
62            edit_log: EditLog::new(),
63        }
64    }
65
66    /// Returns a reference to the tri-matching.
67    pub fn matching(&self) -> &TriMatching {
68        &self.matching
69    }
70
71    /// Runs the 3-way merge and writes the result.
72    ///
73    /// The output is written as XML to the provided writer.
74    pub fn merge<W: Write>(&mut self, writer: &mut W) -> std::io::Result<()> {
75        // Build the merged tree
76        let tree = self.merge_to_tree();
77
78        // Use XmlPrinter for proper text content handling
79        let mut printer = XmlPrinter::new(writer);
80        printer.print(&tree)
81    }
82
83    /// Runs the 3-way merge and returns the merged tree.
84    ///
85    /// This is useful when you need to programmatically inspect or
86    /// further modify the merge result before output.
87    pub fn merge_to_tree(&mut self) -> NodeRef {
88        // Create synthetic root node
89        let root_content = XmlContent::Element(XmlElement::new(
90            "$ROOT$".to_string(),
91            std::collections::HashMap::new(),
92        ));
93        let root = new_node_ref(NodeInner::new_base(Some(root_content)));
94
95        // Build children recursively
96        self.tree_merge_to_node(
97            Some(self.matching.left_root().clone()),
98            Some(self.matching.right_root().clone()),
99            &root,
100        );
101
102        root
103    }
104
105    /// Recursive tree merge that builds nodes instead of writing output.
106    fn tree_merge_to_node(&mut self, a: Option<NodeRef>, b: Option<NodeRef>, parent: &NodeRef) {
107        // Create merge lists for each branch
108        let mlist_a = a.as_ref().map(|node| self.make_merge_list(node));
109        let mlist_b = b.as_ref().map(|node| self.make_merge_list(node));
110
111        // Generate merge pair list
112        let merged = match (&mlist_a, &mlist_b) {
113            (Some(ma), Some(mb)) => self.make_merge_pair_list(ma.clone(), mb.clone()),
114            (Some(ma), None) => self.merge_list_to_pair_list(ma, None),
115            (None, Some(mb)) => self.merge_list_to_pair_list(mb, None),
116            (None, None) => MergePairList::new(),
117        };
118
119        // Process each merge pair
120        for pair in merged.pairs() {
121            let merged_content = self.merge_node_content(pair);
122
123            if let Some(content) = merged_content {
124                match &content {
125                    XmlContent::Element(elem) if elem.qname() == "$ROOT$" => {
126                        // Skip synthetic root but process children
127                        let partners = self.get_recursion_partners(pair);
128                        self.tree_merge_to_node(partners.first, partners.second, parent);
129                    }
130                    _ => {
131                        // Create new node with merged content
132                        let child = new_node_ref(NodeInner::new_base(Some(content.clone())));
133
134                        // Add to parent
135                        NodeInner::add_child_to_ref(parent, child.clone());
136
137                        // Recurse for element children
138                        if let XmlContent::Element(_) = content {
139                            let partners = self.get_recursion_partners(pair);
140                            self.tree_merge_to_node(partners.first, partners.second, &child);
141                        }
142                    }
143                }
144            }
145        }
146    }
147
148    /// Creates a merge list from the children of a branch node.
149    fn make_merge_list(&mut self, parent: &NodeRef) -> MergeList {
150        MergeList::from_branch(parent, &mut self.edit_log)
151    }
152
153    /// Merges two merge lists into a merge pair list.
154    fn make_merge_pair_list(&mut self, mlist_a: MergeList, mlist_b: MergeList) -> MergePairList {
155        let mut mlist_a = mlist_a;
156        let mut mlist_b = mlist_b;
157
158        // Remove deleted or far-moved nodes
159        self.remove_deleted_or_moved(&mut mlist_a, &mut mlist_b);
160
161        // Checkpoint for potential rollback
162        self.edit_log.checkpoint();
163
164        // Lists should now have the same entries
165        if mlist_a.entry_count() != mlist_b.entry_count() {
166            // Fallback to simpler merge
167            self.edit_log.rewind();
168            let is_left = mlist_a
169                .entry_parent()
170                .map(|p| BranchNode::is_left_tree(&p))
171                .unwrap_or(true);
172            return if is_left {
173                self.merge_list_to_pair_list(&mlist_a, Some(&mlist_b))
174            } else {
175                self.merge_list_to_pair_list(&mlist_b, Some(&mlist_a))
176            };
177        }
178
179        let mut merged = MergePairList::new();
180        let mut pos_a = 0usize;
181        let mut pos_b = 0usize;
182
183        loop {
184            let ea = mlist_a.entry(pos_a);
185            let eb = mlist_b.entry(pos_b);
186
187            if ea.is_none() || eb.is_none() {
188                break;
189            }
190
191            let ea = ea.unwrap();
192            let eb = eb.unwrap();
193
194            // Add hangons from ea
195            for hangon in ea.hangons() {
196                let partner = hangon.borrow().first_partner(MatchType::FULL);
197                merged.append(Some(hangon.clone()), partner);
198                self.log_hangon_struct_ops(hangon);
199            }
200
201            // Add hangons from eb (if not equal to ea's hangons)
202            if !self.check_hangon_combine(ea, eb) {
203                for hangon in eb.hangons() {
204                    let partner = hangon.borrow().first_partner(MatchType::FULL);
205                    merged.append(Some(hangon.clone()), partner);
206                    self.log_hangon_struct_ops(hangon);
207                }
208            }
209
210            // Determine next positions
211            let next_a: Option<usize>;
212            let next_b: Option<usize>;
213
214            let ea_locked = ea.locked && mlist_a.entry(pos_a + 1).is_some_and(|e| e.locked);
215            let eb_locked = eb.locked && mlist_b.entry(pos_b + 1).is_some_and(|e| e.locked);
216
217            if !ea_locked && !eb_locked {
218                // No locking, both advance
219                next_a = Some(pos_a + 1);
220                next_b = Some(pos_b + 1);
221            } else if ea_locked && !eb_locked {
222                next_a = Some(pos_a + 1);
223                next_b = mlist_b.find_partner_index(mlist_a.entry(pos_a + 1));
224            } else if !ea_locked && eb_locked {
225                next_b = Some(pos_b + 1);
226                next_a = mlist_a.find_partner_index(mlist_b.entry(pos_b + 1));
227            } else {
228                // Both locked - check for conflict
229                let partner_b = mlist_b.find_partner_index(mlist_a.entry(pos_a + 1));
230                if partner_b != Some(pos_b + 1) {
231                    // Conflict - fallback
232                    self.conflict_log.add_list_conflict(
233                        ConflictType::Move,
234                        "Conflicting moves inside child list",
235                        ea.node().and_then(|n| BranchNode::base_match(&n)),
236                        ea.node(),
237                        eb.node(),
238                    );
239                    self.edit_log.rewind();
240                    let is_left = mlist_a
241                        .entry_parent()
242                        .map(|p| BranchNode::is_left_tree(&p))
243                        .unwrap_or(true);
244                    return if is_left {
245                        self.merge_list_to_pair_list(&mlist_a, Some(&mlist_b))
246                    } else {
247                        self.merge_list_to_pair_list(&mlist_b, Some(&mlist_a))
248                    };
249                }
250                next_a = Some(pos_a + 1);
251                next_b = partner_b;
252            }
253
254            pos_a = next_a.unwrap_or(pos_a + 1);
255            pos_b = next_b.unwrap_or(pos_b + 1);
256
257            // Check if done (at END marker)
258            if mlist_a.is_end(pos_a) || mlist_b.is_end(pos_b) {
259                break;
260            }
261
262            // Add the main entry pair
263            let ea = mlist_a.entry(pos_a);
264            let eb = mlist_b.entry(pos_b);
265            if let (Some(ea), Some(eb)) = (ea, eb) {
266                merged.append(ea.node(), eb.node());
267                self.log_entry_struct_ops(ea, eb);
268            }
269        }
270
271        self.edit_log.commit();
272        merged
273    }
274
275    /// Converts a single merge list to a pair list.
276    fn merge_list_to_pair_list(
277        &mut self,
278        mlist_a: &MergeList,
279        mlist_b: Option<&MergeList>,
280    ) -> MergePairList {
281        let mut merged = MergePairList::new();
282
283        for (i, entry) in mlist_a.entries().iter().enumerate() {
284            // Skip START marker but process its hangons
285            if i > 0 && !mlist_a.is_end(i) {
286                let node = entry.node();
287                let partner = node
288                    .as_ref()
289                    .and_then(|n| n.borrow().first_partner(MatchType::FULL));
290                merged.append(node, partner);
291                if let Some(n) = entry.node() {
292                    self.log_hangon_struct_ops(&n);
293                }
294            }
295
296            // Add hangons
297            for hangon in entry.hangons() {
298                let partner = hangon.borrow().first_partner(MatchType::FULL);
299                merged.append(Some(hangon.clone()), partner);
300                self.log_hangon_struct_ops(hangon);
301            }
302
303            // Add hangons from mlist_b if present
304            if let Some(mb) = mlist_b {
305                if let Some(partner_entry) = mb.find_partner(entry) {
306                    if !self.check_hangon_combine(entry, partner_entry) {
307                        for hangon in partner_entry.hangons() {
308                            let partner = hangon.borrow().first_partner(MatchType::FULL);
309                            merged.append(Some(hangon.clone()), partner);
310                            self.log_hangon_struct_ops(hangon);
311                        }
312                    }
313                }
314            }
315        }
316
317        merged
318    }
319
320    /// Removes deleted or far-moved nodes from merge lists.
321    fn remove_deleted_or_moved(&mut self, mlist_a: &mut MergeList, mlist_b: &mut MergeList) {
322        let base_parent = mlist_a
323            .entry_parent()
324            .and_then(|p| BranchNode::base_match(&p));
325
326        if base_parent.is_none() {
327            return;
328        }
329
330        let base_parent = base_parent.unwrap();
331        let child_count = base_parent.borrow().child_count();
332
333        for i in 0..child_count {
334            let bn = base_parent.borrow().child(i).cloned();
335            if bn.is_none() {
336                continue;
337            }
338            let bn = bn.unwrap();
339
340            let op1 = self.get_operation(&bn, mlist_a);
341            let op2 = self.get_operation(&bn, mlist_b);
342
343            // Handle based on which operation is smaller
344            // The table is symmetric in terms of which list we apply actions to
345            if op1 <= op2 {
346                self.handle_operation_pair(op1, op2, &bn, mlist_a, mlist_b);
347            } else {
348                self.handle_operation_pair(op2, op1, &bn, mlist_b, mlist_a);
349            }
350        }
351    }
352
353    /// Handles a pair of operations on a base node.
354    fn handle_operation_pair(
355        &mut self,
356        op1: Operation,
357        op2: Operation,
358        bn: &NodeRef,
359        ml1: &mut MergeList,
360        _ml2: &mut MergeList,
361    ) {
362        use Operation::*;
363
364        match (op1, op2) {
365            (Nop, Nop) | (Nop, MoveI) | (MoveI, MoveI) | (Delete, Delete) => {
366                // OK cases - no action needed
367            }
368            (Nop, MoveF) | (Nop, Delete) => {
369                // Delete the node from ml1
370                if let Some(ix) = ml1.match_in_list(bn) {
371                    if op2 == Delete {
372                        // Check for modifications in deleted subtree
373                        if let Some(entry) = ml1.entry(ix) {
374                            if let Some(node) = entry.node() {
375                                if self.is_deletia_modified(&node, ml1) {
376                                    self.conflict_log.add_list_warning(
377                                        ConflictType::Delete,
378                                        "Modifications in deleted subtree",
379                                        Some(bn.clone()),
380                                        entry.node(),
381                                        None,
382                                    );
383                                }
384                            }
385                        }
386                        self.edit_log.delete(Some(bn.clone()));
387                    }
388
389                    // Move hangons to predecessor
390                    ml1.move_hangons_to_predecessor(ix);
391                    ml1.remove_entry_at(ix);
392                }
393            }
394            (MoveI, MoveF) => {
395                // Conflict: node moved to different locations
396                if let Some(ix) = ml1.match_in_list(bn) {
397                    let node = ml1.entry(ix).and_then(|e| e.node());
398                    let partner = node
399                        .as_ref()
400                        .and_then(|n| n.borrow().first_partner(MatchType::FULL));
401                    self.conflict_log.add_list_conflict(
402                        ConflictType::Move,
403                        "Node moved to different locations",
404                        Some(bn.clone()),
405                        node,
406                        partner,
407                    );
408                    ml1.remove_entry_at(ix);
409                }
410            }
411            (MoveI, Delete) => {
412                // Conflict: node moved and deleted
413                if let Some(ix) = ml1.match_in_list(bn) {
414                    let node = ml1.entry(ix).and_then(|e| e.node());
415                    self.conflict_log.add_list_conflict(
416                        ConflictType::Move,
417                        "Node moved and deleted",
418                        Some(bn.clone()),
419                        node,
420                        None,
421                    );
422                    self.edit_log.delete(Some(bn.clone()));
423                    ml1.remove_entry_at(ix);
424                }
425            }
426            (MoveF, MoveF) => {
427                // Check for movef-movef conflict
428                if self.is_movef_movef_conflict(bn) {
429                    self.conflict_log.add_list_conflict(
430                        ConflictType::Move,
431                        "Node moved to different locations",
432                        Some(bn.clone()),
433                        BaseNode::left(bn).borrow().full_match(),
434                        BaseNode::right(bn).borrow().full_match(),
435                    );
436                }
437            }
438            (MoveF, Delete) => {
439                // Conflict: moved and deleted - keep the move
440                self.conflict_log.add_list_conflict(
441                    ConflictType::Move,
442                    "Node moved and deleted",
443                    Some(bn.clone()),
444                    BaseNode::left(bn).borrow().full_match(),
445                    BaseNode::right(bn).borrow().full_match(),
446                );
447            }
448            _ => {}
449        }
450    }
451
452    /// Gets the operation on a base node in a merge list.
453    fn get_operation(&self, bn: &NodeRef, ml: &MergeList) -> Operation {
454        if let Some(pos) = ml.match_in_list(bn) {
455            if ml.entry(pos).is_some_and(|e| e.moved) {
456                Operation::MoveI
457            } else {
458                Operation::Nop
459            }
460        } else {
461            // Check if moved far or deleted
462            let is_left = ml
463                .entry_parent()
464                .map(|p| BranchNode::is_left_tree(&p))
465                .unwrap_or(true);
466
467            let copies = if is_left {
468                BaseNode::left(bn)
469            } else {
470                BaseNode::right(bn)
471            };
472
473            if copies.borrow().match_count() == 0 {
474                Operation::Delete
475            } else {
476                Operation::MoveF
477            }
478        }
479    }
480
481    /// Checks if a deleted subtree has modifications.
482    fn is_deletia_modified(&self, n: &NodeRef, ml: &MergeList) -> bool {
483        let base_match = BranchNode::base_match(n);
484        if base_match.is_none() {
485            return true; // Inserted node
486        }
487
488        let base_match = base_match.unwrap();
489        if self.get_operation(&base_match, ml) != Operation::Nop {
490            return true; // Node was moved
491        }
492
493        if BranchNode::base_match_type(n) != MatchType::FULL {
494            return true; // Structural or content modification
495        }
496
497        // Check if deleted in other branch
498        if let Some(partners) = BranchNode::partners(n) {
499            if partners.borrow().match_count() == 0 {
500                if !self.matches(n, &base_match) {
501                    return true; // Updated
502                }
503                // Check children recursively
504                for i in 0..n.borrow().child_count() {
505                    if let Some(child) = n.borrow().child(i).cloned() {
506                        // Would need child's merge list - simplify for now
507                        if BranchNode::base_match_type(&child) != MatchType::FULL {
508                            return true;
509                        }
510                    }
511                }
512            }
513        }
514
515        false
516    }
517
518    /// Checks for movef-movef conflict.
519    ///
520    /// A MoveF-MoveF conflict occurs when both branches move the same base node
521    /// to different parent locations. Returns true if the parents differ (conflict).
522    fn is_movef_movef_conflict(&self, bn: &NodeRef) -> bool {
523        // Get left/right branch copies of base node
524        let left_node = BaseNode::left(bn).borrow().full_match();
525        let right_node = BaseNode::right(bn).borrow().full_match();
526
527        let (left_node, right_node) = match (left_node, right_node) {
528            (Some(l), Some(r)) => (l, r),
529            _ => return false,
530        };
531
532        // Get parents
533        let left_parent = BranchNode::parent(&left_node);
534        let right_parent = BranchNode::parent(&right_node);
535
536        match (left_parent, right_parent) {
537            (Some(lp), Some(rp)) => {
538                let lp_base = BranchNode::base_match(&lp);
539                let rp_base = BranchNode::base_match(&rp);
540                match (lp_base, rp_base) {
541                    (Some(lb), Some(rb)) => lb.borrow().id() != rb.borrow().id(),
542                    _ => true, // Inserted parent = conflict
543                }
544            }
545            (None, None) => false, // Both at root
546            _ => true,             // One at root, one not
547        }
548    }
549
550    /// Merges the content of nodes in a merge pair.
551    fn merge_node_content(&mut self, pair: &MergePair) -> Option<XmlContent> {
552        let n1 = pair.first.as_ref();
553        let n2 = pair.second.as_ref();
554
555        match (n1, n2) {
556            (None, None) => None,
557            (Some(n), None) | (None, Some(n)) => n.borrow().content().cloned(),
558            (Some(n1), Some(n2)) => {
559                let n1_content = n1.borrow().is_match(MatchType::CONTENT);
560                let n2_content = n2.borrow().is_match(MatchType::CONTENT);
561
562                if n1_content {
563                    if !n2_content {
564                        self.edit_log.update(Some(n2.clone()));
565                        n2.borrow().content().cloned()
566                    } else {
567                        self.content_merge(n1, n2)
568                    }
569                } else if n2_content {
570                    self.edit_log.update(Some(n1.clone()));
571                    n1.borrow().content().cloned()
572                } else {
573                    // Neither matches content - forced merge
574                    self.content_merge(n1, n2)
575                }
576            }
577        }
578    }
579
580    /// Merges content of two nodes.
581    fn content_merge(&mut self, a: &NodeRef, b: &NodeRef) -> Option<XmlContent> {
582        let base_a = BranchNode::base_match(a);
583        let base_b = BranchNode::base_match(b);
584
585        let a_updated = base_a.as_ref().is_none_or(|ba| !self.matches(a, ba));
586        let b_updated = base_b.as_ref().is_none_or(|bb| !self.matches(b, bb));
587
588        if a_updated && b_updated {
589            // Both updated - check if same update
590            if self.matches(a, b) {
591                self.conflict_log.add_node_warning(
592                    ConflictType::Update,
593                    "Node updated in both branches, but updates are equal",
594                    base_a.clone(),
595                    Some(a.clone()),
596                    Some(b.clone()),
597                );
598                self.edit_log.update(Some(a.clone()));
599                return a.borrow().content().cloned();
600            }
601
602            // Try element merge
603            if let Some(merged) = self.merge_elements(base_a.as_ref(), a, b) {
604                return Some(merged);
605            }
606
607            // Conflict - use first branch
608            self.conflict_log.add_node_conflict(
609                ConflictType::Update,
610                "Node updated in both branches, using branch 1",
611                base_a,
612                Some(a.clone()),
613                Some(b.clone()),
614            );
615            self.edit_log.update(Some(a.clone()));
616            a.borrow().content().cloned()
617        } else if b_updated {
618            self.edit_log.update(Some(b.clone()));
619            b.borrow().content().cloned()
620        } else if a_updated {
621            self.edit_log.update(Some(a.clone()));
622            a.borrow().content().cloned()
623        } else {
624            a.borrow().content().cloned()
625        }
626    }
627
628    /// Attempts to merge two element nodes.
629    fn merge_elements(
630        &self,
631        base: Option<&NodeRef>,
632        a: &NodeRef,
633        b: &NodeRef,
634    ) -> Option<XmlContent> {
635        let base_content = base.and_then(|n| n.borrow().content().cloned());
636        let a_content = a.borrow().content().cloned();
637        let b_content = b.borrow().content().cloned();
638
639        match (&base_content, &a_content, &b_content) {
640            (
641                Some(XmlContent::Element(base_elem)),
642                Some(XmlContent::Element(a_elem)),
643                Some(XmlContent::Element(b_elem)),
644            ) => {
645                // Determine tag name
646                let tag_name = if base_elem.qname() == b_elem.qname() {
647                    a_elem.qname().to_string()
648                } else if base_elem.qname() == a_elem.qname() {
649                    b_elem.qname().to_string()
650                } else {
651                    return None; // Both changed tag name
652                };
653
654                // Merge attributes
655                self.merge_attributes(
656                    base_elem.attributes(),
657                    a_elem.attributes(),
658                    b_elem.attributes(),
659                )
660                .map(|merged_attrs| XmlContent::Element(XmlElement::new(tag_name, merged_attrs)))
661            }
662            _ => None,
663        }
664    }
665
666    /// Merges attributes from base, a, and b.
667    fn merge_attributes(
668        &self,
669        base: &std::collections::HashMap<String, String>,
670        a: &std::collections::HashMap<String, String>,
671        b: &std::collections::HashMap<String, String>,
672    ) -> Option<std::collections::HashMap<String, String>> {
673        let mut merged = std::collections::HashMap::new();
674        let mut deletia = std::collections::HashSet::new();
675
676        // Check for deleted attributes with conflicts
677        for (key, base_val) in base {
678            let in_a = a.get(key);
679            let in_b = b.get(key);
680
681            match (in_a, in_b) {
682                (None, Some(b_val)) if base_val != b_val => return None, // Deleted and changed
683                (Some(a_val), None) if base_val != a_val => return None, // Deleted and changed
684                (None, _) | (_, None) => {
685                    deletia.insert(key.clone());
686                }
687                _ => {}
688            }
689        }
690
691        // Build merged attributes from a
692        for (key, a_val) in a {
693            if deletia.contains(key) {
694                continue;
695            }
696
697            if let Some(b_val) = b.get(key) {
698                let base_val = base.get(key);
699                if base_val == Some(b_val) {
700                    // A possibly updated
701                    merged.insert(key.clone(), a_val.clone());
702                } else if base_val == Some(a_val) {
703                    // B possibly updated
704                    merged.insert(key.clone(), b_val.clone());
705                } else if a_val != b_val {
706                    // Both changed differently
707                    return None;
708                } else {
709                    merged.insert(key.clone(), a_val.clone());
710                }
711            } else {
712                // Insert from a
713                merged.insert(key.clone(), a_val.clone());
714            }
715        }
716
717        // Add inserts from b
718        for (key, b_val) in b {
719            if !deletia.contains(key) && !a.contains_key(key) {
720                merged.insert(key.clone(), b_val.clone());
721            }
722        }
723
724        Some(merged)
725    }
726
727    /// Gets partners for recursion.
728    fn get_recursion_partners(&self, pair: &MergePair) -> RecursionPartners {
729        let n1 = pair.first.as_ref();
730        let n2 = pair.second.as_ref();
731
732        match (n1, n2) {
733            (None, _) | (_, None) => RecursionPartners {
734                first: pair.first.clone(),
735                second: pair.second.clone(),
736            },
737            (Some(n1), Some(n2)) => {
738                let n1_children = n1.borrow().is_match(MatchType::CHILDREN);
739                let n2_children = n2.borrow().is_match(MatchType::CHILDREN);
740
741                if n1_children && n2_children {
742                    RecursionPartners {
743                        first: Some(n1.clone()),
744                        second: Some(n2.clone()),
745                    }
746                } else if n1_children && n2.borrow().is_match(MatchType::CONTENT) {
747                    RecursionPartners {
748                        first: None,
749                        second: Some(n2.clone()),
750                    }
751                } else if n1.borrow().is_match(MatchType::CONTENT) && n2_children {
752                    RecursionPartners {
753                        first: Some(n1.clone()),
754                        second: None,
755                    }
756                } else {
757                    RecursionPartners {
758                        first: Some(n1.clone()),
759                        second: Some(n2.clone()),
760                    }
761                }
762            }
763        }
764    }
765
766    /// Checks if two nodes have equal content.
767    fn matches(&self, a: &NodeRef, b: &NodeRef) -> bool {
768        let a_borrowed = a.borrow();
769        let b_borrowed = b.borrow();
770        let a_content = a_borrowed.content();
771        let b_content = b_borrowed.content();
772
773        match (a_content, b_content) {
774            (Some(ac), Some(bc)) => ac.content_equals(bc),
775            _ => false,
776        }
777    }
778
779    /// Logs structural operations for a hangon node.
780    fn log_hangon_struct_ops(&mut self, n: &NodeRef) {
781        if !BranchNode::has_base_match(n) {
782            self.edit_log.insert(Some(n.clone()));
783        } else {
784            let base = BranchNode::base_match(n).unwrap();
785            let is_left = BranchNode::is_left_tree(n);
786
787            let copies = if is_left {
788                BaseNode::left(&base)
789            } else {
790                BaseNode::right(&base)
791            };
792
793            if copies.borrow().match_count() > 1 {
794                self.edit_log.copy(Some(n.clone()));
795            } else {
796                self.edit_log.r#move(Some(n.clone()));
797            }
798        }
799    }
800
801    /// Logs structural operations for merge entries.
802    fn log_entry_struct_ops(&mut self, e1: &MergeEntry, e2: &MergeEntry) {
803        if e1.moved {
804            if let Some(n) = e1.node() {
805                self.edit_log.r#move(Some(n));
806            }
807        } else if e2.moved {
808            if let Some(n) = e2.node() {
809                self.edit_log.r#move(Some(n));
810            }
811        }
812    }
813
814    /// Checks if hangons from two entries are equal.
815    /// Returns true only if hangons match exactly (same count and content).
816    /// When false, the caller should add eb's hangons to the merge list.
817    fn check_hangon_combine(&mut self, ea: &MergeEntry, eb: &MergeEntry) -> bool {
818        // If ea has no hangons, return false so eb's hangons are added
819        if ea.hangons().is_empty() {
820            return false;
821        }
822
823        if ea.hangons().len() != eb.hangons().len() {
824            if !ea.hangons().is_empty() && !eb.hangons().is_empty() {
825                self.conflict_log.add_list_warning(
826                    ConflictType::Insert,
827                    "Insertions/copies in both branches",
828                    ea.node().and_then(|n| BranchNode::base_match(&n)),
829                    ea.node(),
830                    eb.node(),
831                );
832            }
833            return false;
834        }
835
836        // Check if hangons match exactly
837        let mut equal = true;
838        for (ha, hb) in ea.hangons().iter().zip(eb.hangons().iter()) {
839            if !self.tree_matches(ha, hb) {
840                equal = false;
841                break;
842            }
843        }
844
845        if equal {
846            self.conflict_log.add_list_warning(
847                ConflictType::Insert,
848                "Equal insertions/copies in both branches",
849                ea.node().and_then(|n| BranchNode::base_match(&n)),
850                ea.node(),
851                eb.node(),
852            );
853        } else if !ea.hangons().is_empty() {
854            self.conflict_log.add_list_warning(
855                ConflictType::Insert,
856                "Insertions/copies in both branches, sequencing",
857                ea.node().and_then(|n| BranchNode::base_match(&n)),
858                ea.node(),
859                eb.node(),
860            );
861        }
862
863        equal
864    }
865
866    /// Checks if entire subtrees match exactly.
867    fn tree_matches(&self, a: &NodeRef, b: &NodeRef) -> bool {
868        if !self.matches(a, b) {
869            return false;
870        }
871
872        let a_count = a.borrow().child_count();
873        let b_count = b.borrow().child_count();
874
875        if a_count != b_count {
876            return false;
877        }
878
879        for i in 0..a_count {
880            let a_child = a.borrow().child(i).cloned();
881            let b_child = b.borrow().child(i).cloned();
882
883            match (a_child, b_child) {
884                (Some(ac), Some(bc)) => {
885                    if !self.tree_matches(&ac, &bc) {
886                        return false;
887                    }
888                }
889                _ => return false,
890            }
891        }
892
893        true
894    }
895}
896
897/// Partners for recursion.
898struct RecursionPartners {
899    first: Option<NodeRef>,
900    second: Option<NodeRef>,
901}
902
903impl crate::node::NodeInner {
904    /// Returns the first partner that matches the given type.
905    pub fn first_partner(&self, type_flags: MatchType) -> Option<NodeRef> {
906        if let crate::node::NodeKind::Branch {
907            match_type,
908            partners,
909            ..
910        } = self.kind()
911        {
912            if !match_type.intersects(type_flags) {
913                return None;
914            }
915
916            if let Some(partners) = partners {
917                let borrowed = partners.borrow();
918                for candidate in borrowed.matches() {
919                    if BranchNode::base_match_type(candidate).intersects(type_flags) {
920                        return Some(candidate.clone());
921                    }
922                }
923            }
924        }
925        None
926    }
927
928    /// Returns true if the match type includes the given flags.
929    pub fn is_match(&self, type_flags: MatchType) -> bool {
930        if let crate::node::NodeKind::Branch { match_type, .. } = self.kind() {
931            match_type.intersects(type_flags)
932        } else {
933            false
934        }
935    }
936}