gitscope
← rgit / 0.1.3 / src/database/schema/repository.rs
https://dev.kronoz.win/r/rgit.git
src/database/schema/repository.rs · Rust
Blame Raw
  1use std::{borrow::Cow, collections::BTreeMap, ops::Deref, path::Path, sync::Arc};
  2
  3use anyhow::{Context, Result};
  4use rand::random;
  5use rocksdb::IteratorMode;
  6use serde::{Deserialize, Serialize};
  7use time::OffsetDateTime;
  8use yoke::{Yoke, Yokeable};
  9
 10use crate::database::schema::{
 11    commit::CommitTree,
 12    prefixes::{COMMIT_FAMILY, REFERENCE_FAMILY, REPOSITORY_FAMILY, TAG_FAMILY},
 13    tag::TagTree,
 14    Yoked,
 15};
 16
 17#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Yokeable)]
 18pub struct Repository<'a> {
 19    /// The ID of the repository, as stored in `RocksDB`
 20    pub id: RepositoryId,
 21    /// The "clean name" of the repository (ie. `hello-world.git`)
 22    #[serde(borrow)]
 23    pub name: Cow<'a, str>,
 24    /// The description of the repository, as it is stored in the `description` file in the
 25    /// bare repo root
 26    #[serde(borrow)]
 27    pub description: Option<Cow<'a, str>>,
 28    /// The owner of the repository (`gitweb.owner` in the repository configuration)
 29    #[serde(borrow)]
 30    pub owner: Option<Cow<'a, str>>,
 31    /// The last time this repository was updated, currently read from the directory mtime
 32    pub last_modified: OffsetDateTime,
 33    /// The default branch for Git operations
 34    #[serde(borrow)]
 35    pub default_branch: Option<Cow<'a, str>>,
 36}
 37
 38pub type YokedRepository = Yoked<Repository<'static>>;
 39
 40impl Repository<'_> {
 41    pub fn exists<P: AsRef<Path>>(database: &rocksdb::DB, path: P) -> Result<bool> {
 42        let cf = database
 43            .cf_handle(REPOSITORY_FAMILY)
 44            .context("repository column family missing")?;
 45        let path = path.as_ref().to_str().context("invalid path")?;
 46
 47        Ok(database.get_pinned_cf(cf, path)?.is_some())
 48    }
 49
 50    pub fn fetch_all(database: &rocksdb::DB) -> Result<BTreeMap<String, YokedRepository>> {
 51        let cf = database
 52            .cf_handle(REPOSITORY_FAMILY)
 53            .context("repository column family missing")?;
 54
 55        database
 56            .full_iterator_cf(cf, IteratorMode::Start)
 57            .filter_map(Result::ok)
 58            .map(|(key, value)| {
 59                let key = String::from_utf8(key.into_vec()).context("invalid repo name")?;
 60                let value = Yoke::try_attach_to_cart(value, |data| bincode::deserialize(data))?;
 61
 62                Ok((key, value))
 63            })
 64            .collect()
 65    }
 66
 67    pub fn insert<P: AsRef<Path>>(&self, database: &rocksdb::DB, path: P) -> Result<()> {
 68        let cf = database
 69            .cf_handle(REPOSITORY_FAMILY)
 70            .context("repository column family missing")?;
 71        let path = path.as_ref().to_str().context("invalid path")?;
 72
 73        database.put_cf(cf, path, bincode::serialize(self)?)?;
 74
 75        Ok(())
 76    }
 77
 78    pub fn delete<P: AsRef<Path>>(&self, database: &rocksdb::DB, path: P) -> Result<()> {
 79        let start_id = self.id.to_be_bytes();
 80        let mut end_id = self.id.to_be_bytes();
 81        *end_id.last_mut().unwrap() += 1;
 82
 83        // delete commits
 84        let commit_cf = database
 85            .cf_handle(COMMIT_FAMILY)
 86            .context("commit column family missing")?;
 87        database.delete_range_cf(commit_cf, start_id, end_id)?;
 88
 89        // delete tags
 90        let tag_cf = database
 91            .cf_handle(TAG_FAMILY)
 92            .context("tag column family missing")?;
 93        database.delete_range_cf(tag_cf, start_id, end_id)?;
 94
 95        // delete self
 96        let repo_cf = database
 97            .cf_handle(REPOSITORY_FAMILY)
 98            .context("repository column family missing")?;
 99        let path = path.as_ref().to_str().context("invalid path")?;
100        database.delete_cf(repo_cf, path)?;
101
102        Ok(())
103    }
104
105    pub fn open<P: AsRef<Path>>(
106        database: &rocksdb::DB,
107        path: P,
108    ) -> Result<Option<YokedRepository>> {
109        let cf = database
110            .cf_handle(REPOSITORY_FAMILY)
111            .context("repository column family missing")?;
112
113        let path = path.as_ref().to_str().context("invalid path")?;
114        let Some(value) = database.get_cf(cf, path)? else {
115            return Ok(None);
116        };
117
118        Yoke::try_attach_to_cart(value.into_boxed_slice(), |data| bincode::deserialize(data))
119            .map(Some)
120            .context("Failed to open repository")
121    }
122
123    pub fn commit_tree(&self, database: Arc<rocksdb::DB>, reference: &str) -> CommitTree {
124        CommitTree::new(database, self.id, reference)
125    }
126
127    pub fn tag_tree(&self, database: Arc<rocksdb::DB>) -> TagTree {
128        TagTree::new(database, self.id)
129    }
130
131    pub fn replace_heads(&self, database: &rocksdb::DB, new_heads: &[String]) -> Result<()> {
132        let cf = database
133            .cf_handle(REFERENCE_FAMILY)
134            .context("missing reference column family")?;
135
136        database.put_cf(cf, self.id.to_be_bytes(), bincode::serialize(new_heads)?)?;
137
138        Ok(())
139    }
140
141    pub fn heads(&self, database: &rocksdb::DB) -> Result<Yoke<Vec<String>, Box<[u8]>>> {
142        let cf = database
143            .cf_handle(REFERENCE_FAMILY)
144            .context("missing reference column family")?;
145
146        let Some(bytes) = database.get_cf(cf, self.id.to_be_bytes())? else {
147            return Ok(Yoke::attach_to_cart(Box::default(), |_| vec![]));
148        };
149
150        Yoke::try_attach_to_cart(Box::from(bytes), |bytes| bincode::deserialize(bytes))
151            .context("failed to deserialize heads")
152    }
153}
154
155#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Hash)]
156pub struct RepositoryId(pub(super) u64);
157
158impl RepositoryId {
159    pub fn new() -> Self {
160        Self(random())
161    }
162}
163
164impl Deref for RepositoryId {
165    type Target = u64;
166
167    fn deref(&self) -> &Self::Target {
168        &self.0
169    }
170}