gitcc_git/
tag.rs

1//! Tags
2
3use time::OffsetDateTime;
4
5use crate::{error::Error, util::convert_git2_time, GitRepository};
6
7/// A git tag
8#[derive(Debug, Clone)]
9pub struct Tag {
10    /// ID (hash)
11    pub id: String,
12    /// Date
13    pub date: OffsetDateTime,
14    /// Name (short)
15    pub name: String,
16    /// Full name
17    pub name_full: String,
18    /// Tag message - None if lightweight tag
19    pub message: Option<String>,
20    /// Commit ID (hash)
21    pub commit_id: String,
22}
23
24impl PartialEq for Tag {
25    fn eq(&self, other: &Self) -> bool {
26        self.id == other.id
27    }
28}
29
30impl Tag {
31    /// Checks if the tag is annotated
32    pub fn is_annotated(&self) -> bool {
33        self.message.is_some()
34    }
35}
36
37/// Retrieves all the repo tags (lightweight and annotated)
38pub fn list_tags(repo: &GitRepository) -> Result<Vec<String>, Error> {
39    let tags = repo.tag_names(None)?;
40    let tags = tags
41        .into_iter()
42        .filter_map(|t| t.map(|s| s.to_string()))
43        .collect();
44    Ok(tags)
45}
46
47/// Retrieves all the repo tag references
48///
49/// This method looks for all references and finds the tags.
50pub fn get_tag_refs(repo: &GitRepository) -> Result<Vec<Tag>, Error> {
51    let refs = repo.references()?;
52
53    let mut tags = vec![];
54    for res in refs {
55        let rf = res?;
56
57        // resolve symbolic tags
58        let rf = rf.resolve()?;
59
60        // extract data
61        let id = rf.target().unwrap().to_string();
62        let full_name = rf.name().unwrap_or("__invalid__").to_string();
63        let name = rf.shorthand().unwrap_or("__invalid__").to_string();
64
65        // a tag starts with 'refs/tags'
66        // if it is an annotated tag, it is possible to peel back to a Tag
67        // eprintln!("ref: {full_name}");
68        if !full_name.starts_with("refs/tags/") {
69            // eprintln!("not a tag => skipped");
70            continue;
71        }
72        // peel to tag to check if the ref is a tag
73        // NB: lightweight tags do not have ref of their own
74        let tag = rf.peel_to_tag().ok();
75        let tag_message = tag.map(|t| t.message().unwrap_or("__invalid__").trim().to_string());
76
77        // peel to find the commit
78        // NB: a tag always points to a commit (itself for a lightweight tag)
79        let commit = rf.peel_to_commit()?;
80        let commit_id = commit.id().to_string();
81        let date = convert_git2_time(commit.time())?;
82
83        tags.push({
84            Tag {
85                id,
86                date,
87                name,
88                name_full: full_name,
89                message: tag_message,
90                commit_id,
91            }
92        })
93    }
94
95    Ok(tags)
96}
97
98/// Sets an annotated tag to the HEAD
99pub fn set_annotated_tag(repo: &GitRepository, tag: &str, message: &str) -> Result<(), Error> {
100    let head_commit = repo.head()?.peel_to_commit()?;
101    let tagger = repo.signature()?;
102    let _oid = repo.tag(tag, head_commit.as_object(), &tagger, message, false)?;
103    Ok(())
104}
105
106#[cfg(test)]
107mod tests {
108    use crate::repo::discover_repo;
109
110    use super::*;
111
112    #[test]
113    fn test_tags_simple() {
114        let cwd = std::env::current_dir().unwrap();
115        let repo = discover_repo(&cwd).unwrap();
116        let tags = list_tags(&repo).unwrap();
117        for tag in tags {
118            eprintln!("{tag}")
119        }
120    }
121
122    #[test]
123    fn test_tags_refs() {
124        let cwd = std::env::current_dir().unwrap();
125        let repo = discover_repo(&cwd).unwrap();
126        let tags = get_tag_refs(&repo).unwrap();
127        for tag in tags {
128            eprintln!("{}:  {} ({})", tag.id, tag.name, tag.commit_id)
129        }
130    }
131}