Skip to main content

git_spawn/command/
status.rs

1//! `git status` — show the working tree status.
2
3use crate::command::{CommandExecutor, CommandOutput, GitCommand};
4use crate::error::Result;
5use async_trait::async_trait;
6
7/// Porcelain v2 formats and friends.
8#[derive(Debug, Clone, Copy)]
9pub enum StatusFormat {
10    /// Short `-s` format.
11    Short,
12    /// Long (default) format.
13    Long,
14    /// `--porcelain=v1`.
15    PorcelainV1,
16    /// `--porcelain=v2`.
17    PorcelainV2,
18}
19
20/// Builder for `git status`.
21#[derive(Debug, Clone, Default)]
22pub struct StatusCommand {
23    /// Shared executor.
24    pub executor: CommandExecutor,
25    /// Output format override.
26    pub format: Option<StatusFormat>,
27    /// `--branch` / `-b`.
28    pub branch: bool,
29    /// `--show-stash`.
30    pub show_stash: bool,
31    /// NUL-terminate entries (`-z`).
32    pub null_terminate: bool,
33    /// `--untracked-files=<mode>`.
34    pub untracked_files: Option<String>,
35    /// `--ignored`.
36    pub ignored: bool,
37    /// Pathspec filters.
38    pub paths: Vec<String>,
39}
40
41impl StatusCommand {
42    /// New status command.
43    #[must_use]
44    pub fn new() -> Self {
45        Self::default()
46    }
47
48    /// Set output format.
49    pub fn format(&mut self, f: StatusFormat) -> &mut Self {
50        self.format = Some(f);
51        self
52    }
53
54    /// Include branch info.
55    pub fn branch(&mut self) -> &mut Self {
56        self.branch = true;
57        self
58    }
59
60    /// Show stash count.
61    pub fn show_stash(&mut self) -> &mut Self {
62        self.show_stash = true;
63        self
64    }
65
66    /// NUL-separate entries.
67    pub fn null_terminate(&mut self) -> &mut Self {
68        self.null_terminate = true;
69        self
70    }
71
72    /// Set untracked-file mode (`no`, `normal`, `all`).
73    pub fn untracked_files(&mut self, mode: impl Into<String>) -> &mut Self {
74        self.untracked_files = Some(mode.into());
75        self
76    }
77
78    /// Also show ignored files.
79    pub fn ignored(&mut self) -> &mut Self {
80        self.ignored = true;
81        self
82    }
83
84    /// Filter by path.
85    pub fn path(&mut self, p: impl Into<String>) -> &mut Self {
86        self.paths.push(p.into());
87        self
88    }
89}
90
91#[async_trait]
92impl GitCommand for StatusCommand {
93    type Output = CommandOutput;
94
95    fn get_executor(&self) -> &CommandExecutor {
96        &self.executor
97    }
98
99    fn get_executor_mut(&mut self) -> &mut CommandExecutor {
100        &mut self.executor
101    }
102
103    fn build_command_args(&self) -> Vec<String> {
104        let mut args = vec!["status".to_string()];
105        match self.format {
106            Some(StatusFormat::Short) => args.push("--short".into()),
107            Some(StatusFormat::Long) => args.push("--long".into()),
108            Some(StatusFormat::PorcelainV1) => args.push("--porcelain=v1".into()),
109            Some(StatusFormat::PorcelainV2) => args.push("--porcelain=v2".into()),
110            None => {}
111        }
112        if self.branch {
113            args.push("--branch".into());
114        }
115        if self.show_stash {
116            args.push("--show-stash".into());
117        }
118        if self.null_terminate {
119            args.push("-z".into());
120        }
121        if let Some(m) = &self.untracked_files {
122            args.push(format!("--untracked-files={m}"));
123        }
124        if self.ignored {
125            args.push("--ignored".into());
126        }
127        if !self.paths.is_empty() {
128            args.push("--".into());
129            args.extend(self.paths.iter().cloned());
130        }
131        args
132    }
133
134    async fn execute(&self) -> Result<CommandOutput> {
135        self.execute_raw().await
136    }
137}