Skip to main content

crate_seq_git/
discover.rs

1//! Git tag discovery with glob-pattern filtering and `SemVer` parsing.
2
3use crate::Error;
4
5/// A resolved git tag with a parsed `SemVer` version.
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct TagRef {
8    /// Full tag name, e.g. `"v1.0.3"` or `"foo-v1.0.3"`.
9    pub name: String,
10    /// Parsed `SemVer` version.
11    pub semver: semver::Version,
12}
13
14/// Derives the literal prefix from a glob pattern by stripping the trailing `*`.
15fn pattern_prefix(pattern: &str) -> &str {
16    pattern.strip_suffix('*').unwrap_or(pattern)
17}
18
19/// Extracts the short tag name from a full ref name, stripping `refs/tags/`.
20fn short_tag_name(full_name: &gix::bstr::BStr) -> Option<&str> {
21    let stripped = full_name.strip_prefix(b"refs/tags/")?;
22    std::str::from_utf8(stripped).ok()
23}
24
25/// Discovers all git tags in `repo_path` matching `pattern` and parses their `SemVer`.
26///
27/// `pattern` is a glob like `"v*"` or `"foo-v*"`. Tags that do not match or whose
28/// suffix fails `SemVer` parsing are silently skipped. Results are sorted ascending
29/// by `SemVer`.
30///
31/// # Errors
32///
33/// Returns `Error::OpenRepo` if the repository cannot be opened, or reference
34/// iteration errors if the git storage is unreadable.
35pub fn discover_tags(repo_path: &std::path::Path, pattern: &str) -> Result<Vec<TagRef>, Error> {
36    let repo = gix::discover(repo_path).map_err(|e| Error::OpenRepo {
37        path: repo_path.to_owned(),
38        source: Box::new(e),
39    })?;
40
41    let prefix = pattern_prefix(pattern);
42    let mut tags: Vec<TagRef> = repo
43        .references()?
44        .tags()?
45        .filter_map(|result| {
46            let reference = result.ok()?;
47            let full = reference.name().as_bstr();
48            let short = short_tag_name(full)?;
49            let version_str = short.strip_prefix(prefix)?;
50            let semver = semver::Version::parse(version_str).ok()?;
51            Some(TagRef {
52                name: short.to_owned(),
53                semver,
54            })
55        })
56        .collect();
57
58    tags.sort_by(|a, b| a.semver.cmp(&b.semver));
59    Ok(tags)
60}