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