(original: &str, hunks: &[Hunk])
| 637 | } |
| 638 | |
| 639 | fn apply_hunks(original: &str, hunks: &[Hunk]) -> Result<String, ToolError> { |
| 640 | let mut lines: Vec<String> = original.lines().map(|s| s.to_string()).collect(); |
| 641 | let mut offset = 0isize; |
| 642 | |
| 643 | for hunk in hunks { |
| 644 | // Validate hunk line counts against declared counts |
| 645 | let actual_old = hunk |
| 646 | .lines |
| 647 | .iter() |
| 648 | .filter(|l| matches!(l, PatchLine::Remove(_) | PatchLine::Context(_))) |
| 649 | .count(); |
| 650 | let actual_new = hunk |
| 651 | .lines |
| 652 | .iter() |
| 653 | .filter(|l| matches!(l, PatchLine::Add(_) | PatchLine::Context(_))) |
| 654 | .count(); |
| 655 | |
| 656 | if hunk.old_count > 0 && actual_old != hunk.old_count { |
| 657 | tracing::warn!( |
| 658 | "Hunk old_count mismatch: declared {} but found {} lines", |
| 659 | hunk.old_count, |
| 660 | actual_old |
| 661 | ); |
| 662 | } |
| 663 | if hunk.new_count > 0 && actual_new != hunk.new_count { |
| 664 | tracing::warn!( |
| 665 | "Hunk new_count mismatch: declared {} but found {} lines", |
| 666 | hunk.new_count, |
| 667 | actual_new |
| 668 | ); |
| 669 | } |
| 670 | |
| 671 | let start = (hunk.old_start as isize + offset - 1).max(0) as usize; |
| 672 | |
| 673 | // Cross-check: expected new position should align with new_start |
| 674 | let expected_new_pos = (hunk.new_start as isize - 1).max(0) as usize; |
| 675 | if hunk.new_start > 0 && start != expected_new_pos && offset != 0 { |
| 676 | tracing::debug!( |
| 677 | "Hunk position drift: old_start={} + offset={} = {}, new_start={}", |
| 678 | hunk.old_start, |
| 679 | offset, |
| 680 | start + 1, |
| 681 | hunk.new_start |
| 682 | ); |
| 683 | } |
| 684 | |
| 685 | let mut remove_count = 0; |
| 686 | let add_lines: Vec<String> = hunk |
| 687 | .lines |
| 688 | .iter() |
| 689 | .filter_map(|l| match l { |
| 690 | PatchLine::Add(s) => Some(s.clone()), |
| 691 | _ => None, |
| 692 | }) |
| 693 | .collect(); |
| 694 | |
| 695 | for line in &hunk.lines { |
| 696 | if matches!(line, PatchLine::Remove(_)) { |
no test coverage detected