Three-way merge of byte content. Given three versions of a file: - `base` — the common ancestor (A's original content) - `ours` — our modified version (A' after revise) - `theirs` — their modified version (A+B content after B was applied to A) This computes B's delta via `diff(base, theirs)`, then applies that delta to `ours`. For each region: - **Equal** (B didn't change): keep whatever `
(base: &[u8], ours: &[u8], theirs: &[u8])
| 608 | /// assert_eq!(merged, b"line alpha revised\nline beta\n"); |
| 609 | /// ``` |
| 610 | pub fn merge_text(base: &[u8], ours: &[u8], theirs: &[u8]) -> Result<Vec<u8>, String> { |
| 611 | let base_lines: Vec<Line> = Line::from_bytes(base); |
| 612 | let ours_lines: Vec<Line> = Line::from_bytes(ours); |
| 613 | let theirs_lines: Vec<Line> = Line::from_bytes(theirs); |
| 614 | |
| 615 | // Compute what "they" changed relative to the common base. |
| 616 | let b_delta = diff(&base_lines, &theirs_lines, Algorithm::Myers); |
| 617 | |
| 618 | // Compute what "we" changed relative to the common base. |
| 619 | let a_delta = diff(&base_lines, &ours_lines, Algorithm::Myers); |
| 620 | |
| 621 | // Build a set of base line indices that "we" modified (deleted or replaced). |
| 622 | // If B also touches any of these lines, that's a conflict. |
| 623 | let mut our_changed: std::collections::HashSet<usize> = std::collections::HashSet::new(); |
| 624 | for op in a_delta.ops() { |
| 625 | match op { |
| 626 | DiffOp::Delete { old_pos, len, .. } |
| 627 | | DiffOp::Replace { |
| 628 | old_pos, |
| 629 | old_len: len, |
| 630 | .. |
| 631 | } => { |
| 632 | for i in *old_pos..(*old_pos + *len) { |
| 633 | our_changed.insert(i); |
| 634 | } |
| 635 | } |
| 636 | _ => {} |
| 637 | } |
| 638 | } |
| 639 | |
| 640 | // Walk B's delta and build the merged output. |
| 641 | // |
| 642 | // We track our position in `ours_lines` via `ours_idx`. For Equal |
| 643 | // regions we advance ours_idx by the number of base lines consumed |
| 644 | // (which maps 1:1 to ours lines in unchanged regions). For regions |
| 645 | // B changed, we check for conflicts against our_changed. |
| 646 | let mut output: Vec<u8> = Vec::new(); |
| 647 | let mut base_idx: usize = 0; // current position in base |
| 648 | let mut ours_idx: usize = 0; // current position in ours |
| 649 | |
| 650 | for op in b_delta.ops() { |
| 651 | match op { |
| 652 | DiffOp::Equal { old_pos, len, .. } => { |
| 653 | // B didn't change this region. Emit from ours (which may |
| 654 | // have A' modifications for these lines). |
| 655 | // First, advance to old_pos in base. |
| 656 | let skip = old_pos - base_idx; |
| 657 | // ours_idx should already be aligned; advance by the |
| 658 | // same number of lines we skip in base. |
| 659 | ours_idx += skip; |
| 660 | base_idx = *old_pos; |
| 661 | |
| 662 | // Emit `len` lines from ours at ours_idx. |
| 663 | for i in 0..*len { |
| 664 | if ours_idx + i < ours_lines.len() { |
| 665 | output.extend_from_slice(ours_lines[ours_idx + i].content()); |
| 666 | } |
| 667 | } |