use std::{collections::HashSet, sync::Arc}; use anyhow::Context; use git2::Signature; use serde::{Deserialize, Serialize}; use yoke::{Yoke, Yokeable}; use crate::database::schema::{ commit::Author, prefixes::TAG_FAMILY, repository::RepositoryId, Yoked, }; #[derive(Serialize, Deserialize, Debug, Yokeable)] pub struct Tag<'a> { #[serde(borrow)] pub tagger: Option>, } impl<'a> Tag<'a> { pub fn new(tagger: Option<&'a Signature<'_>>) -> Self { Self { tagger: tagger.map(Into::into), } } pub fn insert(&self, batch: &TagTree, name: &str) -> Result<(), anyhow::Error> { batch.insert(name, self) } } pub struct TagTree { db: Arc, prefix: RepositoryId, } pub type YokedTag = Yoked>; impl TagTree { pub(super) fn new(db: Arc, prefix: RepositoryId) -> Self { Self { db, prefix } } pub fn insert(&self, name: &str, value: &Tag<'_>) -> anyhow::Result<()> { let cf = self .db .cf_handle(TAG_FAMILY) .context("missing tag column family")?; let mut db_name = self.prefix.to_be_bytes().to_vec(); db_name.extend_from_slice(name.as_ref()); self.db.put_cf(cf, db_name, bincode::serialize(value)?)?; Ok(()) } pub fn remove(&self, name: &str) -> anyhow::Result<()> { let cf = self .db .cf_handle(TAG_FAMILY) .context("missing tag column family")?; let mut db_name = self.prefix.to_be_bytes().to_vec(); db_name.extend_from_slice(name.as_ref()); self.db.delete_cf(cf, db_name)?; Ok(()) } pub fn list(&self) -> anyhow::Result> { let cf = self .db .cf_handle(TAG_FAMILY) .context("missing tag column family")?; Ok(self .db .prefix_iterator_cf(cf, self.prefix.to_be_bytes()) .filter_map(Result::ok) .filter_map(|(k, _)| { Some( String::from_utf8_lossy(k.strip_prefix(&self.prefix.to_be_bytes())?) .to_string(), ) }) .collect()) } pub fn fetch_all(&self) -> anyhow::Result> { let cf = self .db .cf_handle(TAG_FAMILY) .context("missing tag column family")?; let mut res = self .db .prefix_iterator_cf(cf, self.prefix.to_be_bytes()) .filter_map(Result::ok) .filter_map(|(name, value)| { let name = String::from_utf8_lossy(name.strip_prefix(&self.prefix.to_be_bytes())?) .strip_prefix("refs/tags/")? .to_string(); Some((name, value)) }) .map(|(name, value)| { let value = Yoke::try_attach_to_cart(value, |data| bincode::deserialize(data))?; Ok((name, value)) }) .collect::>>()?; res.sort_unstable_by(|a, b| { let a_tagger = a.1.get().tagger.as_ref().map(|v| v.time); let b_tagger = b.1.get().tagger.as_ref().map(|v| v.time); b_tagger.cmp(&a_tagger) }); Ok(res) } }