Build the real per-file unified diff for a recorded change WITHOUT printing. This is the computation `atomic diff -c ` runs, factored out so other surfaces (e.g. the triage report) can embed actual code hunks instead of re-implementing diffing. It returns every changed file's [`FileDiff`] plus the aggregate [`DiffStats`], using the semantic layer (FileOps) with true hunk offsets and before-
(
repo: &Repository,
change: &Change,
change_hash: &Hash,
config: &DiffOutputConfig,
)
| 884 | /// `atomic diff` path applies `filter_file_diffs` afterward, which yields the |
| 885 | /// same result as the previous in-loop filtering. |
| 886 | pub(crate) fn change_file_diffs( |
| 887 | repo: &Repository, |
| 888 | change: &Change, |
| 889 | change_hash: &Hash, |
| 890 | config: &DiffOutputConfig, |
| 891 | ) -> CliResult<(Vec<FileDiff>, DiffStats)> { |
| 892 | // Git-imported changes carry Git's captured +/- lines in unhashed metadata. |
| 893 | if let Some((file_diffs, stats)) = Diff::build_git_import_file_diffs(change) { |
| 894 | return Ok((file_diffs, stats)); |
| 895 | } |
| 896 | |
| 897 | let file_ops = change.file_ops(); |
| 898 | |
| 899 | let mut file_diffs = Vec::new(); |
| 900 | let mut stats = DiffStats::new(); |
| 901 | |
| 902 | for ops in file_ops { |
| 903 | let file_path = ops.path(); |
| 904 | |
| 905 | let trunk_op = ops.trunk_op(); |
| 906 | let line_ops = ops.line_ops(); |
| 907 | |
| 908 | // Determine file change status from trunk operation |
| 909 | let change_status = match trunk_op { |
| 910 | Some(TrunkOp::Create { .. }) => FileChangeStatus::Added, |
| 911 | Some(TrunkOp::Delete { .. }) => FileChangeStatus::Deleted, |
| 912 | Some(TrunkOp::Move { .. }) => FileChangeStatus::Renamed, |
| 913 | Some(TrunkOp::Undelete { .. }) => FileChangeStatus::Modified, |
| 914 | None => FileChangeStatus::Modified, |
| 915 | }; |
| 916 | |
| 917 | let mut file_diff = match change_status { |
| 918 | FileChangeStatus::Added => FileDiff::added(file_path), |
| 919 | FileChangeStatus::Deleted => FileDiff::deleted(file_path), |
| 920 | FileChangeStatus::Renamed => FileDiff::modified(file_path), |
| 921 | _ => FileDiff::modified(file_path), |
| 922 | }; |
| 923 | |
| 924 | // Pass 1: flatten line operations into numbered changed lines. |
| 925 | let mut insertions = 0usize; |
| 926 | let mut deletions = 0usize; |
| 927 | let mut new_line_num = 1usize; |
| 928 | let mut old_line_num = 1usize; |
| 929 | let mut net: isize = 0; |
| 930 | let mut changed: Vec<NumberedLine> = Vec::new(); |
| 931 | |
| 932 | for line_op in line_ops { |
| 933 | match line_op.operation() { |
| 934 | BranchOp::Insert { content, .. } => { |
| 935 | let line_content = Diff::reconstruct_line_from_leaf_ops(content); |
| 936 | let line_num = line_op.new_line_num().unwrap_or(new_line_num); |
| 937 | changed.push(NumberedLine::added(line_content, line_num, net)); |
| 938 | new_line_num = line_num + 1; |
| 939 | insertions += 1; |
| 940 | net += 1; |
| 941 | } |
| 942 | BranchOp::Delete { content, .. } => { |
| 943 | let line_content = if content.is_empty() { |
no test coverage detected