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