Skip to main content

grit_lib/diff/
detail.rs

1//! Per-file diff detail levels (patch, stat, name-only) and aggregate totals.
2
3use super::options::DiffOptions;
4use super::structured::StructuredFileDiff;
5use super::{count_changes_with_algorithm, DiffEntry, DiffStatus};
6
7/// Count insertions and deletions between two text sides using [`DiffOptions`]'s algorithm.
8///
9/// Does not allocate hunk structures (used for `--stat` / `--numstat`).
10#[must_use]
11pub fn line_change_counts(old_content: &str, new_content: &str, options: &DiffOptions) -> LineStat {
12    let (insertions, deletions) = count_changes_with_algorithm(
13        old_content,
14        new_content,
15        options.algorithm.similar_algorithm(),
16        options.algorithm.uses_git_histogram(),
17    );
18    LineStat {
19        insertions,
20        deletions,
21    }
22}
23
24/// How much content to compute for each changed file.
25#[non_exhaustive]
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
27pub enum DiffDetail {
28    /// Full structured line hunks.
29    #[default]
30    Patch,
31    /// Insertion/deletion counts only (`--stat`, `--numstat`, `--shortstat`).
32    Stat,
33    /// `git diff --name-status` (status letter and paths).
34    NameStatus,
35    /// `git diff --name-only` (paths only).
36    NameOnly,
37}
38
39/// Line insertion/deletion counts for one file (`--numstat`).
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
41pub struct LineStat {
42    /// Lines added on the new side.
43    pub insertions: usize,
44    /// Lines removed from the old side.
45    pub deletions: usize,
46}
47
48/// Summary across all files in a stat-style diff.
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
50pub struct DiffTotals {
51    /// Number of changed paths.
52    pub files_changed: usize,
53    /// Total insertions across text files.
54    pub insertions: usize,
55    /// Total deletions across text files.
56    pub deletions: usize,
57}
58
59impl DiffTotals {
60    /// Sum per-file stats into totals (binary files contribute to `files_changed` only).
61    #[must_use]
62    pub fn from_files(files: &[FileDiff]) -> Self {
63        let mut insertions = 0usize;
64        let mut deletions = 0usize;
65        for file in files {
66            if let Some(stat) = file.stat {
67                insertions += stat.insertions;
68                deletions += stat.deletions;
69            }
70        }
71        Self {
72            files_changed: files.len(),
73            insertions,
74            deletions,
75        }
76    }
77}
78
79/// One changed file with optional patch body or line counts.
80#[derive(Debug, Clone, PartialEq, Eq)]
81pub struct FileDiff {
82    /// Display path (new side, or old for deletions).
83    pub path: String,
84    /// Pre-rename path when it differs from [`Self::path`].
85    pub old_path: Option<String>,
86    /// Change kind.
87    pub status: DiffStatus,
88    /// Old git mode (octal), when present.
89    pub old_mode: Option<String>,
90    /// New git mode (octal), when present.
91    pub new_mode: Option<String>,
92    /// Either side looks like a binary blob.
93    pub binary: bool,
94    /// Rename/copy similarity (0–100), when applicable.
95    pub similarity: Option<u32>,
96    /// Line counts when [`DiffDetail::Stat`] was requested.
97    pub stat: Option<LineStat>,
98    /// Structured hunks when [`DiffDetail::Patch`] was requested.
99    pub structured: Option<StructuredFileDiff>,
100    /// Combined (`--cc`) hunks for merge commits; mutually exclusive with [`Self::structured`].
101    pub combined: Option<crate::combined_diff_patch::CombinedStructuredTextDiff>,
102    /// Per-parent status letters for combined merge name-status (e.g. `MM`, `DD`).
103    pub combined_status: Option<String>,
104}
105
106impl DiffEntry {
107    /// Similarity index for renames and copies (`DiffEntry::score`).
108    #[must_use]
109    pub fn rename_similarity(&self) -> Option<u32> {
110        match self.status {
111            DiffStatus::Renamed | DiffStatus::Copied => self.score,
112            _ => None,
113        }
114    }
115}