evobench_tools/
git_tags.rs

1//! Extension for the `run-git` crate to retrieve git tags
2//!
3//! Probably to be moved there at some point.
4
5use std::{collections::BTreeMap, str::FromStr, sync::Arc};
6
7use anyhow::{Context, Result, anyhow};
8use kstring::KString;
9use run_git::git::GitWorkingDir;
10use smallvec::{SmallVec, smallvec};
11
12use crate::{git::GitHash, warn};
13
14pub struct GitTag {
15    pub name: KString,
16    pub commit: Arc<GitHash>,
17}
18
19pub struct GitTags {
20    tags: Vec<GitTag>,
21    // To index in `tags`
22    by_hash: BTreeMap<Arc<GitHash>, SmallVec<[u32; 1]>>,
23}
24
25impl GitTags {
26    pub fn from_dir(working_dir: &GitWorkingDir) -> Result<Self> {
27        let s = working_dir.git_stdout_string_trimmed(&[
28            "tag", "--list", // -l
29        ])?;
30        let mut tags = Vec::new();
31        let mut by_hash: BTreeMap<Arc<GitHash>, SmallVec<[u32; 1]>> = Default::default();
32        for name in s.split("\n") {
33            if let Some(commit) = working_dir
34                .git_rev_parse(name, true)
35                .with_context(|| anyhow!("resolving tag name {name:?}"))?
36            {
37                let commit = Arc::new(
38                    GitHash::from_str(&commit)
39                        .with_context(|| anyhow!("resolving tag name {name:?}"))?,
40                );
41                let id = u32::try_from(tags.len())
42                    .context("getting tags, you appear to have more than u32::MAX tags")?;
43                match by_hash.entry(commit.clone()) {
44                    std::collections::btree_map::Entry::Vacant(vacant_entry) => {
45                        vacant_entry.insert(smallvec![id]);
46                    }
47                    std::collections::btree_map::Entry::Occupied(mut occupied_entry) => {
48                        occupied_entry.get_mut().push(id);
49                    }
50                }
51
52                let name = KString::from_ref(name);
53                tags.push(GitTag { name, commit });
54            } else {
55                // This *can* happen, there is a race between getting
56                // the tag listing and another process deleting the
57                // tags.
58                warn!("could not resolve tag name {name:?} that we just got");
59            }
60        }
61        Ok(GitTags { tags, by_hash })
62    }
63
64    /// Returns the empty set for unknown commit ids. Returns tag
65    /// names in the order in which they appear in `git tag --list`.
66    pub fn get_by_commit(&self, commit_id: &GitHash) -> impl ExactSizeIterator<Item = &str> {
67        if let Some(items) = self.by_hash.get(commit_id) {
68            &**items
69        } else {
70            &[]
71        }
72        .iter()
73        .map(|i| &*self.tags[usize::try_from(*i).expect("usize expected to be >= u32")].name)
74    }
75}