use std::collections::BTreeMap; use anyhow::Context; use gix::{bstr::BStr, ObjectId}; use itertools::{Either, Itertools}; use rkyv::{Archive, Serialize}; use rocksdb::{WriteBatch, DB}; use yoke::{Yoke, Yokeable}; use super::{ prefixes::{TREE_FAMILY, TREE_ITEM_FAMILY}, Yoked, }; #[derive(Serialize, Archive, Debug, PartialEq, Eq, Hash)] pub struct Tree { pub indexed_tree_id: u64, } impl Tree { pub fn insert( &self, database: &DB, batch: &mut WriteBatch, tree_oid: ObjectId, ) -> Result<(), anyhow::Error> { let cf = database .cf_handle(TREE_FAMILY) .context("tree column family missing")?; batch.put_cf( cf, tree_oid.as_slice(), rkyv::to_bytes::(self)?, ); Ok(()) } pub fn find(database: &DB, tree_oid: ObjectId) -> Result, anyhow::Error> { let cf = database .cf_handle(TREE_FAMILY) .context("tree column family missing")?; let Some(data) = database.get_cf(cf, tree_oid.as_slice())? else { return Ok(None); }; let data = rkyv::access::<::Archived, rkyv::rancor::Error>(data.as_ref())?; Ok(Some(data.indexed_tree_id.to_native())) } } #[derive(Serialize, Archive, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)] #[rkyv(derive(Ord, PartialOrd, Eq, PartialEq, Debug))] #[rkyv(compare(PartialOrd, PartialEq))] pub struct TreeKey(pub String); #[derive(Serialize, Archive, Debug, PartialEq, Eq, Default, Yokeable)] pub struct SortedTree(pub BTreeMap); impl SortedTree { pub fn insert( &self, digest: u64, database: &DB, batch: &mut WriteBatch, ) -> Result<(), anyhow::Error> { let cf = database .cf_handle(TREE_ITEM_FAMILY) .context("tree column family missing")?; batch.put_cf( cf, digest.to_ne_bytes(), rkyv::to_bytes::(self)?, ); Ok(()) } pub fn get(digest: u64, database: &DB) -> Result, anyhow::Error> { let cf = database .cf_handle(TREE_ITEM_FAMILY) .expect("tree column family missing"); database .get_cf(cf, digest.to_ne_bytes())? .map(|data| { Yoke::try_attach_to_cart(data.into_boxed_slice(), |data| { rkyv::access::<_, rkyv::rancor::Error>(data) }) }) .transpose() .context("failed to parse full tree") } } #[derive(Serialize, Archive, Debug, PartialEq, Eq)] #[rkyv( bytecheck(bounds(__C: rkyv::validation::ArchiveContext)), serialize_bounds(__S: rkyv::ser::Writer + rkyv::ser::Allocator, __S::Error: rkyv::rancor::Source), )] pub enum SortedTreeItem { File, Directory(#[rkyv(omit_bounds)] SortedTree), } #[derive(Serialize, Archive, Debug, PartialEq, Eq, Hash)] pub struct Submodule { pub url: String, pub oid: [u8; 20], } #[derive(Serialize, Archive, Debug, PartialEq, Eq, Hash)] pub enum TreeItemKind { Submodule(Submodule), Tree, File, } #[derive(Serialize, Archive, Debug, PartialEq, Eq, Hash, Yokeable)] pub struct TreeItem { pub mode: u16, pub kind: TreeItemKind, } pub type YokedSortedTree = Yoked<&'static ::Archived>; pub type YokedTreeItem = Yoked<&'static ::Archived>; pub type YokedTreeItemKey = Yoked<&'static [u8]>; pub type YokedTreeItemKeyUtf8 = Yoked<&'static str>; impl TreeItem { pub fn insert( &self, buffer: &mut Vec, digest: u64, path: &BStr, database: &DB, batch: &mut WriteBatch, ) -> Result<(), anyhow::Error> { let cf = database .cf_handle(TREE_ITEM_FAMILY) .context("tree column family missing")?; buffer.clear(); buffer.reserve(std::mem::size_of::() + path.len() + std::mem::size_of::()); buffer.extend_from_slice(&digest.to_ne_bytes()); buffer.extend_from_slice(&memchr::memchr_iter(b'/', path).count().to_be_bytes()); buffer.extend_from_slice(path.as_ref()); batch.put_cf(cf, &buffer, rkyv::to_bytes::(self)?); Ok(()) } pub fn find_exact( database: &DB, digest: u64, path: &[u8], ) -> Result, anyhow::Error> { let cf = database .cf_handle(TREE_ITEM_FAMILY) .expect("tree column family missing"); let mut buffer = Vec::with_capacity(std::mem::size_of::() + path.len()); buffer.extend_from_slice(&digest.to_ne_bytes()); buffer.extend_from_slice(&memchr::memchr_iter(b'/', path).count().to_be_bytes()); buffer.extend_from_slice(path); database .get_cf(cf, buffer)? .map(|data| { Yoke::try_attach_to_cart(data.into_boxed_slice(), |data| { rkyv::access::<_, rkyv::rancor::Error>(data) }) }) .transpose() .context("failed to parse tree item") } pub fn find_prefix<'a>( database: &'a DB, digest: u64, prefix: Option<&[u8]>, ) -> impl Iterator> + use<'a> { let cf = database .cf_handle(TREE_ITEM_FAMILY) .expect("tree column family missing"); let (iterator, key) = match prefix { None => { let iterator = database.prefix_iterator_cf(cf, digest.to_ne_bytes()); (iterator, Either::Left(Either::Left(digest.to_be_bytes()))) } Some([]) => { let mut buffer = [0_u8; std::mem::size_of::() + std::mem::size_of::()]; buffer[..std::mem::size_of::()].copy_from_slice(&digest.to_ne_bytes()); buffer[std::mem::size_of::()..].copy_from_slice(&0_usize.to_be_bytes()); let iterator = database.prefix_iterator_cf(cf, buffer); (iterator, Either::Left(Either::Right(buffer))) } Some(prefix) => { let mut buffer = Vec::with_capacity( std::mem::size_of::() + prefix.len() + std::mem::size_of::(), ); buffer.extend_from_slice(&digest.to_ne_bytes()); buffer.extend_from_slice( &(memchr::memchr_iter(b'/', prefix).count() + 1).to_be_bytes(), ); buffer.extend_from_slice(prefix); buffer.push(b'/'); let iterator = database.prefix_iterator_cf(cf, &buffer); (iterator, Either::Right(buffer)) } }; iterator .take_while(move |v| { v.as_ref().is_ok_and(|(k, _)| { k.starts_with(match key.as_ref() { Either::Left(Either::Right(v)) => v.as_ref(), Either::Left(Either::Left(v)) => v.as_ref(), Either::Right(v) => v.as_ref(), }) }) }) .map_ok(|(key, value)| { let key = Yoke::attach_to_cart(key, |data| { &data[std::mem::size_of::() + std::mem::size_of::()..] }); let value = Yoke::try_attach_to_cart(value, |data| { rkyv::access::<_, rkyv::rancor::Error>(data) }) .context("Failed to open repository")?; Ok((key, value)) }) .flatten() } pub fn contains(database: &DB, digest: u64) -> Result { let cf = database .cf_handle(TREE_ITEM_FAMILY) .context("tree column family missing")?; Ok(database .prefix_iterator_cf(cf, digest.to_ne_bytes()) .next() .transpose()? .is_some()) } }