Skip to main content

git_spawn/command/
diff.rs

1//! `git diff` — show changes between commits, trees, and the working tree.
2
3use crate::command::{CommandExecutor, CommandOutput, GitCommand};
4use crate::error::Result;
5use async_trait::async_trait;
6
7/// Builder for `git diff`.
8#[derive(Debug, Clone, Default)]
9pub struct DiffCommand {
10    /// Shared executor.
11    pub executor: CommandExecutor,
12    /// `--cached` / `--staged`.
13    pub cached: bool,
14    /// `--name-only`.
15    pub name_only: bool,
16    /// `--name-status`.
17    pub name_status: bool,
18    /// `--stat`.
19    pub stat: bool,
20    /// `--shortstat`.
21    pub shortstat: bool,
22    /// `--numstat`.
23    pub numstat: bool,
24    /// `--no-color`.
25    pub no_color: bool,
26    /// `--unified=N`.
27    pub unified: Option<u32>,
28    /// Revisions (e.g. `HEAD~1 HEAD`).
29    pub revisions: Vec<String>,
30    /// Pathspec filters.
31    pub paths: Vec<String>,
32}
33
34impl DiffCommand {
35    /// New `diff` command.
36    #[must_use]
37    pub fn new() -> Self {
38        Self::default()
39    }
40
41    /// Show staged changes.
42    pub fn cached(&mut self) -> &mut Self {
43        self.cached = true;
44        self
45    }
46
47    /// `--name-only`.
48    pub fn name_only(&mut self) -> &mut Self {
49        self.name_only = true;
50        self
51    }
52
53    /// `--name-status`.
54    pub fn name_status(&mut self) -> &mut Self {
55        self.name_status = true;
56        self
57    }
58
59    /// `--stat`.
60    pub fn stat(&mut self) -> &mut Self {
61        self.stat = true;
62        self
63    }
64
65    /// `--shortstat`.
66    pub fn shortstat(&mut self) -> &mut Self {
67        self.shortstat = true;
68        self
69    }
70
71    /// `--numstat`.
72    pub fn numstat(&mut self) -> &mut Self {
73        self.numstat = true;
74        self
75    }
76
77    /// Disable color.
78    pub fn no_color(&mut self) -> &mut Self {
79        self.no_color = true;
80        self
81    }
82
83    /// Context lines (`-U`).
84    pub fn unified(&mut self, n: u32) -> &mut Self {
85        self.unified = Some(n);
86        self
87    }
88
89    /// Add a revision.
90    pub fn revision(&mut self, r: impl Into<String>) -> &mut Self {
91        self.revisions.push(r.into());
92        self
93    }
94
95    /// Filter by path.
96    pub fn path(&mut self, p: impl Into<String>) -> &mut Self {
97        self.paths.push(p.into());
98        self
99    }
100}
101
102#[async_trait]
103impl GitCommand for DiffCommand {
104    type Output = CommandOutput;
105
106    fn get_executor(&self) -> &CommandExecutor {
107        &self.executor
108    }
109
110    fn get_executor_mut(&mut self) -> &mut CommandExecutor {
111        &mut self.executor
112    }
113
114    fn build_command_args(&self) -> Vec<String> {
115        let mut args = vec!["diff".to_string()];
116        if self.cached {
117            args.push("--cached".into());
118        }
119        if self.name_only {
120            args.push("--name-only".into());
121        }
122        if self.name_status {
123            args.push("--name-status".into());
124        }
125        if self.stat {
126            args.push("--stat".into());
127        }
128        if self.shortstat {
129            args.push("--shortstat".into());
130        }
131        if self.numstat {
132            args.push("--numstat".into());
133        }
134        if self.no_color {
135            args.push("--no-color".into());
136        }
137        if let Some(u) = self.unified {
138            args.push(format!("--unified={u}"));
139        }
140        args.extend(self.revisions.iter().cloned());
141        if !self.paths.is_empty() {
142            args.push("--".into());
143            args.extend(self.paths.iter().cloned());
144        }
145        args
146    }
147
148    async fn execute(&self) -> Result<CommandOutput> {
149        self.execute_raw().await
150    }
151}