Skip to main content

abbaye/version_extractors/
git.rs

1use miette::{IntoDiagnostic, Result, miette};
2use serde::Deserialize;
3
4use super::VersionExtractor;
5
6fn default_dirty_suffix() -> String {
7    "-dirty".to_owned()
8}
9
10/// Configuration for [`GitVersion`].
11#[derive(Debug, Clone, Deserialize)]
12pub struct GitVersionConfig {
13    /// Strip this prefix from the tag name before returning the version.
14    /// For example, `"v"` turns `"v1.2.3"` into `"1.2.3"`.
15    pub tag_prefix: Option<String>,
16
17    /// Suffix appended to the version when the working tree has uncommitted
18    /// changes. Forwarded verbatim as `--dirty=<suffix>` to `git describe`.
19    /// Defaults to `"-dirty"`.
20    #[serde(default = "default_dirty_suffix")]
21    pub dirty_suffix: String,
22}
23
24impl Default for GitVersionConfig {
25    fn default() -> Self {
26        Self {
27            tag_prefix: None,
28            dirty_suffix: default_dirty_suffix(),
29        }
30    }
31}
32
33/// Extracts the version by running `git describe --tags --always`.
34pub struct GitVersion;
35
36impl VersionExtractor for GitVersion {
37    type ConfigType = GitVersionConfig;
38
39    async fn get_last_version(&self, config: Self::ConfigType) -> Result<String> {
40        let dirty_arg = format!("--dirty={}", config.dirty_suffix);
41
42        let output = tokio::process::Command::new("git")
43            .args(["describe", "--tags", "--always", &dirty_arg])
44            .output()
45            .await
46            .into_diagnostic()?;
47
48        if !output.status.success() {
49            let stderr = String::from_utf8_lossy(&output.stderr);
50            return Err(miette!("git describe failed:\n{stderr}"));
51        }
52
53        let raw = String::from_utf8(output.stdout).into_diagnostic()?;
54        let version = raw.trim();
55
56        let version = match &config.tag_prefix {
57            Some(prefix) => version.strip_prefix(prefix.as_str()).unwrap_or(version),
58            None => version,
59        };
60
61        Ok(version.to_owned())
62    }
63
64    async fn get_all_versions(&self, config: Self::ConfigType) -> Result<Vec<String>> {
65        let output = tokio::process::Command::new("git")
66            .args(["tag", "--list", "--sort=version:refname"])
67            .output()
68            .await
69            .into_diagnostic()?;
70
71        if !output.status.success() {
72            let stderr = String::from_utf8_lossy(&output.stderr);
73            return Err(miette!("git tag failed:\n{stderr}"));
74        }
75
76        let raw = String::from_utf8(output.stdout).into_diagnostic()?;
77
78        let versions = raw
79            .lines()
80            .filter(|s| !s.is_empty())
81            .map(|tag| match &config.tag_prefix {
82                Some(prefix) => tag.strip_prefix(prefix.as_str()).unwrap_or(tag).to_owned(),
83                None => tag.to_owned(),
84            })
85            .collect();
86
87        Ok(versions)
88    }
89}