1use std::{collections::HashSet, sync::Arc};
2
3use anyhow::Context;
4use git2::Signature;
5use serde::{Deserialize, Serialize};
6use yoke::{Yoke, Yokeable};
7
8use crate::database::schema::{
9 commit::Author, prefixes::TAG_FAMILY, repository::RepositoryId, Yoked,
10};
11
12#[derive(Serialize, Deserialize, Debug, Yokeable)]
13pub struct Tag<'a> {
14 #[serde(borrow)]
15 pub tagger: Option<Author<'a>>,
16}
17
18impl<'a> Tag<'a> {
19 pub fn new(tagger: Option<&'a Signature<'_>>) -> Self {
20 Self {
21 tagger: tagger.map(Into::into),
22 }
23 }
24
25 pub fn insert(&self, batch: &TagTree, name: &str) -> Result<(), anyhow::Error> {
26 batch.insert(name, self)
27 }
28}
29
30pub struct TagTree {
31 db: Arc<rocksdb::DB>,
32 prefix: RepositoryId,
33}
34
35pub type YokedTag = Yoked<Tag<'static>>;
36
37impl TagTree {
38 pub(super) fn new(db: Arc<rocksdb::DB>, prefix: RepositoryId) -> Self {
39 Self { db, prefix }
40 }
41
42 pub fn insert(&self, name: &str, value: &Tag<'_>) -> anyhow::Result<()> {
43 let cf = self
44 .db
45 .cf_handle(TAG_FAMILY)
46 .context("missing tag column family")?;
47
48 let mut db_name = self.prefix.to_be_bytes().to_vec();
49 db_name.extend_from_slice(name.as_ref());
50
51 self.db.put_cf(cf, db_name, bincode::serialize(value)?)?;
52
53 Ok(())
54 }
55
56 pub fn remove(&self, name: &str) -> anyhow::Result<()> {
57 let cf = self
58 .db
59 .cf_handle(TAG_FAMILY)
60 .context("missing tag column family")?;
61
62 let mut db_name = self.prefix.to_be_bytes().to_vec();
63 db_name.extend_from_slice(name.as_ref());
64 self.db.delete_cf(cf, db_name)?;
65
66 Ok(())
67 }
68
69 pub fn list(&self) -> anyhow::Result<HashSet<String>> {
70 let cf = self
71 .db
72 .cf_handle(TAG_FAMILY)
73 .context("missing tag column family")?;
74
75 Ok(self
76 .db
77 .prefix_iterator_cf(cf, self.prefix.to_be_bytes())
78 .filter_map(Result::ok)
79 .filter_map(|(k, _)| {
80 Some(
81 String::from_utf8_lossy(k.strip_prefix(&self.prefix.to_be_bytes())?)
82 .to_string(),
83 )
84 })
85 .collect())
86 }
87
88 pub fn fetch_all(&self) -> anyhow::Result<Vec<(String, YokedTag)>> {
89 let cf = self
90 .db
91 .cf_handle(TAG_FAMILY)
92 .context("missing tag column family")?;
93
94 let mut res = self
95 .db
96 .prefix_iterator_cf(cf, self.prefix.to_be_bytes())
97 .filter_map(Result::ok)
98 .filter_map(|(name, value)| {
99 let name = String::from_utf8_lossy(name.strip_prefix(&self.prefix.to_be_bytes())?)
100 .strip_prefix("refs/tags/")?
101 .to_string();
102
103 Some((name, value))
104 })
105 .map(|(name, value)| {
106 let value = Yoke::try_attach_to_cart(value, |data| bincode::deserialize(data))?;
107 Ok((name, value))
108 })
109 .collect::<anyhow::Result<Vec<(String, YokedTag)>>>()?;
110
111 res.sort_unstable_by(|a, b| {
112 let a_tagger = a.1.get().tagger.as_ref().map(|v| v.time);
113 let b_tagger = b.1.get().tagger.as_ref().map(|v| v.time);
114 b_tagger.cmp(&a_tagger)
115 });
116
117 Ok(res)
118 }
119}