Skip to main content

changeset_git/repository/
tag.rs

1use crate::{Result, TagInfo};
2
3use super::Repository;
4
5impl Repository {
6    /// Deletes a tag by name.
7    ///
8    /// Returns `Ok(true)` if the tag was deleted, `Ok(false)` if the tag was not found.
9    ///
10    /// # Errors
11    ///
12    /// Returns an error if the delete operation fails for reasons other than "not found".
13    pub fn delete_tag(&self, name: &str) -> Result<bool> {
14        let refname = format!("refs/tags/{name}");
15        match self.inner.find_reference(&refname) {
16            Ok(mut reference) => {
17                reference.delete()?;
18                Ok(true)
19            }
20            Err(e) if e.code() == git2::ErrorCode::NotFound => Ok(false),
21            Err(e) => Err(e.into()),
22        }
23    }
24
25    /// # Errors
26    ///
27    /// Returns an error if the tag cannot be created or already exists.
28    pub fn create_tag(&self, name: &str, message: &str) -> Result<TagInfo> {
29        let head = self.inner.head()?.peel_to_commit()?;
30        let sig = self.inner.signature()?;
31
32        self.inner
33            .tag(name, head.as_object(), &sig, message, false)?;
34
35        Ok(TagInfo {
36            name: name.to_string(),
37            target_sha: head.id().to_string(),
38        })
39    }
40}
41
42#[cfg(test)]
43mod tests {
44    use super::super::tests::setup_test_repo;
45
46    #[test]
47    fn create_annotated_tag() -> anyhow::Result<()> {
48        let (_dir, repo) = setup_test_repo()?;
49
50        let tag_info = repo.create_tag("v1.0.0", "Release version 1.0.0")?;
51
52        assert_eq!(tag_info.name, "v1.0.0");
53
54        let head = repo.inner.head()?.peel_to_commit()?;
55        assert_eq!(tag_info.target_sha, head.id().to_string());
56
57        let tag = repo.inner.find_reference("refs/tags/v1.0.0")?;
58        assert!(tag.peel_to_tag().is_ok());
59
60        Ok(())
61    }
62
63    #[test]
64    fn create_tag_with_crate_prefix() -> anyhow::Result<()> {
65        let (_dir, repo) = setup_test_repo()?;
66
67        let tag_info = repo.create_tag("my-crate-v0.1.0", "Release my-crate version 0.1.0")?;
68
69        assert_eq!(tag_info.name, "my-crate-v0.1.0");
70
71        let tag = repo.inner.find_reference("refs/tags/my-crate-v0.1.0")?;
72        assert!(tag.peel_to_tag().is_ok());
73
74        Ok(())
75    }
76
77    #[test]
78    fn duplicate_tag_fails() -> anyhow::Result<()> {
79        let (_dir, repo) = setup_test_repo()?;
80
81        repo.create_tag("v1.0.0", "First tag")?;
82        let result = repo.create_tag("v1.0.0", "Duplicate tag");
83
84        assert!(result.is_err());
85
86        Ok(())
87    }
88
89    #[test]
90    fn delete_existing_tag_returns_true() -> anyhow::Result<()> {
91        let (_dir, repo) = setup_test_repo()?;
92
93        repo.create_tag("v1.0.0", "Tag to delete")?;
94        assert!(repo.inner.find_reference("refs/tags/v1.0.0").is_ok());
95
96        let deleted = repo.delete_tag("v1.0.0")?;
97
98        assert!(deleted);
99        assert!(repo.inner.find_reference("refs/tags/v1.0.0").is_err());
100
101        Ok(())
102    }
103
104    #[test]
105    fn delete_nonexistent_tag_returns_false() -> anyhow::Result<()> {
106        let (_dir, repo) = setup_test_repo()?;
107
108        let deleted = repo.delete_tag("nonexistent-tag")?;
109
110        assert!(!deleted);
111
112        Ok(())
113    }
114}