Skip to main content

git_spawn/command/
describe.rs

1//! `git describe` — describe a commit using the most recent reachable tag.
2
3use crate::command::{CommandExecutor, GitCommand};
4use crate::error::Result;
5use async_trait::async_trait;
6
7/// Builder for `git describe`.
8#[derive(Debug, Clone, Default)]
9pub struct DescribeCommand {
10    /// Shared executor.
11    pub executor: CommandExecutor,
12    /// Commit-ish(es) to describe.
13    pub commits: Vec<String>,
14    /// `--tags` include lightweight tags.
15    pub tags: bool,
16    /// `--all` include all refs, not just tags.
17    pub all: bool,
18    /// `--always` fall back to abbreviated SHA when nothing matches.
19    pub always: bool,
20    /// `--long` always show the long form (`tag-count-gSHA`).
21    pub long: bool,
22    /// `--dirty[=<mark>]`.
23    pub dirty: Option<Option<String>>,
24    /// `--abbrev=N`.
25    pub abbrev: Option<u32>,
26    /// `--match=<pattern>`.
27    pub match_pattern: Option<String>,
28    /// `--exclude=<pattern>`.
29    pub exclude: Option<String>,
30    /// `--first-parent`.
31    pub first_parent: bool,
32}
33
34impl DescribeCommand {
35    /// New describe command.
36    #[must_use]
37    pub fn new() -> Self {
38        Self::default()
39    }
40
41    /// Add a commit-ish to describe.
42    pub fn commit(&mut self, c: impl Into<String>) -> &mut Self {
43        self.commits.push(c.into());
44        self
45    }
46
47    /// `--tags`.
48    pub fn tags(&mut self) -> &mut Self {
49        self.tags = true;
50        self
51    }
52
53    /// `--all`.
54    pub fn all(&mut self) -> &mut Self {
55        self.all = true;
56        self
57    }
58
59    /// `--always`.
60    pub fn always(&mut self) -> &mut Self {
61        self.always = true;
62        self
63    }
64
65    /// `--long`.
66    pub fn long(&mut self) -> &mut Self {
67        self.long = true;
68        self
69    }
70
71    /// `--dirty` with default mark (`-dirty`).
72    pub fn dirty(&mut self) -> &mut Self {
73        self.dirty = Some(None);
74        self
75    }
76
77    /// `--dirty=<mark>`.
78    pub fn dirty_mark(&mut self, mark: impl Into<String>) -> &mut Self {
79        self.dirty = Some(Some(mark.into()));
80        self
81    }
82
83    /// `--abbrev=N`.
84    pub fn abbrev(&mut self, n: u32) -> &mut Self {
85        self.abbrev = Some(n);
86        self
87    }
88
89    /// `--match=<pattern>`.
90    pub fn match_pattern(&mut self, p: impl Into<String>) -> &mut Self {
91        self.match_pattern = Some(p.into());
92        self
93    }
94
95    /// `--exclude=<pattern>`.
96    pub fn exclude(&mut self, p: impl Into<String>) -> &mut Self {
97        self.exclude = Some(p.into());
98        self
99    }
100
101    /// `--first-parent`.
102    pub fn first_parent(&mut self) -> &mut Self {
103        self.first_parent = true;
104        self
105    }
106}
107
108#[async_trait]
109impl GitCommand for DescribeCommand {
110    /// Trimmed description string, e.g. `v1.2.3-5-gabc1234`.
111    type Output = String;
112
113    fn get_executor(&self) -> &CommandExecutor {
114        &self.executor
115    }
116
117    fn get_executor_mut(&mut self) -> &mut CommandExecutor {
118        &mut self.executor
119    }
120
121    fn build_command_args(&self) -> Vec<String> {
122        let mut args = vec!["describe".to_string()];
123        if self.tags {
124            args.push("--tags".into());
125        }
126        if self.all {
127            args.push("--all".into());
128        }
129        if self.always {
130            args.push("--always".into());
131        }
132        if self.long {
133            args.push("--long".into());
134        }
135        if self.first_parent {
136            args.push("--first-parent".into());
137        }
138        match &self.dirty {
139            Some(None) => args.push("--dirty".into()),
140            Some(Some(mark)) => args.push(format!("--dirty={mark}")),
141            None => {}
142        }
143        if let Some(n) = self.abbrev {
144            args.push(format!("--abbrev={n}"));
145        }
146        if let Some(p) = &self.match_pattern {
147            args.push(format!("--match={p}"));
148        }
149        if let Some(p) = &self.exclude {
150            args.push(format!("--exclude={p}"));
151        }
152        args.extend(self.commits.iter().cloned());
153        args
154    }
155
156    async fn execute(&self) -> Result<String> {
157        let out = self.execute_raw().await?;
158        Ok(out.stdout_trimmed().to_string())
159    }
160}