gitscope
← rgit / 0.1.5 / 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    /// Whether the repository is available for HTTP(s) cloning
 32    ///
 33    /// This is set to `true` based on the presence of `git-daemon-export-ok` in the repository
 34    pub exported: bool,
 35}
 36
 37pub type YokedRepository = Yoked<&'static <Repository as Archive>::Archived>;
 38
 39impl Repository {
 40    pub fn exists<P: AsRef<Path>>(database: &rocksdb::DB, path: P) -> Result<bool> {
 41        let cf = database
 42            .cf_handle(REPOSITORY_FAMILY)
 43            .context("repository column family missing")?;
 44        let path = path.as_ref().to_str().context("invalid path")?;
 45
 46        Ok(database.get_pinned_cf(cf, path)?.is_some())
 47    }
 48
 49    pub fn fetch_all(database: &rocksdb::DB) -> Result<BTreeMap<String, YokedRepository>> {
 50        let cf = database
 51            .cf_handle(REPOSITORY_FAMILY)
 52            .context("repository column family missing")?;
 53
 54        database
 55            .iterator_cf(cf, IteratorMode::Start)
 56            .filter_map(Result::ok)
 57            .map(|(key, value)| {
 58                let key = String::from_utf8(key.into_vec()).context("invalid repo name")?;
 59                let value = Yoke::try_attach_to_cart(value, |data| {
 60                    rkyv::access::<_, rkyv::rancor::Error>(data)
 61                })?;
 62
 63                Ok((key, value))
 64            })
 65            .collect()
 66    }
 67
 68    pub fn insert<P: AsRef<Path>>(&self, database: &rocksdb::DB, path: P) -> Result<()> {
 69        let cf = database
 70            .cf_handle(REPOSITORY_FAMILY)
 71            .context("repository column family missing")?;
 72        let path = path.as_ref().to_str().context("invalid path")?;
 73
 74        database.put_cf(cf, path, rkyv::to_bytes::<rkyv::rancor::Error>(self)?)?;
 75
 76        Ok(())
 77    }
 78
 79    pub fn open<P: AsRef<Path>>(
 80        database: &rocksdb::DB,
 81        path: P,
 82    ) -> Result<Option<YokedRepository>> {
 83        let cf = database
 84            .cf_handle(REPOSITORY_FAMILY)
 85            .context("repository column family missing")?;
 86
 87        let path = path.as_ref().to_str().context("invalid path")?;
 88        let Some(value) = database.get_cf(cf, path)? else {
 89            return Ok(None);
 90        };
 91
 92        Yoke::try_attach_to_cart(value.into_boxed_slice(), |data| {
 93            rkyv::access::<_, rkyv::rancor::Error>(data)
 94        })
 95        .map(Some)
 96        .context("Failed to open repository")
 97    }
 98}
 99
100impl ArchivedRepository {
101    pub fn delete<P: AsRef<Path>>(&self, database: &rocksdb::DB, path: P) -> Result<()> {
102        let start_id = self.id.0.to_native().to_be_bytes();
103        let mut end_id = start_id;
104        *end_id.last_mut().unwrap() += 1;
105
106        // delete commits
107        let commit_cf = database
108            .cf_handle(COMMIT_FAMILY)
109            .context("commit column family missing")?;
110        database.delete_range_cf(commit_cf, start_id, end_id)?;
111
112        // delete tags
113        let tag_cf = database
114            .cf_handle(TAG_FAMILY)
115            .context("tag column family missing")?;
116        database.delete_range_cf(tag_cf, start_id, end_id)?;
117
118        // delete self
119        let repo_cf = database
120            .cf_handle(REPOSITORY_FAMILY)
121            .context("repository column family missing")?;
122        let path = path.as_ref().to_str().context("invalid path")?;
123        database.delete_cf(repo_cf, path)?;
124
125        Ok(())
126    }
127
128    pub fn commit_tree(&self, database: Arc<rocksdb::DB>, reference: &str) -> CommitTree {
129        CommitTree::new(database, RepositoryId(self.id.0.to_native()), reference)
130    }
131
132    pub fn tag_tree(&self, database: Arc<rocksdb::DB>) -> TagTree {
133        TagTree::new(database, RepositoryId(self.id.0.to_native()))
134    }
135
136    pub fn replace_heads(&self, database: &rocksdb::DB, new_heads: &Vec<String>) -> Result<()> {
137        let cf = database
138            .cf_handle(REFERENCE_FAMILY)
139            .context("missing reference column family")?;
140
141        database.put_cf(
142            cf,
143            self.id.0.to_native().to_be_bytes(),
144            rkyv::to_bytes::<rkyv::rancor::Error>(new_heads)?,
145        )?;
146
147        Ok(())
148    }
149
150    #[allow(clippy::type_complexity)]
151    pub fn heads(
152        &self,
153        database: &rocksdb::DB,
154    ) -> Result<Option<Yoke<&'static ArchivedHeads, Box<[u8]>>>> {
155        let cf = database
156            .cf_handle(REFERENCE_FAMILY)
157            .context("missing reference column family")?;
158
159        let Some(bytes) = database.get_cf(cf, self.id.0.to_native().to_be_bytes())? else {
160            return Ok(None);
161        };
162
163        Yoke::try_attach_to_cart(Box::from(bytes), |bytes| {
164            rkyv::access::<_, rkyv::rancor::Error>(bytes)
165        })
166        .context("failed to deserialize heads")
167        .map(Some)
168    }
169}
170
171#[derive(Serialize, Archive, Debug, Clone, PartialEq, Eq, Hash)]
172pub struct Heads(pub Vec<String>);
173
174#[derive(Serialize, Archive, Debug, Copy, Clone, PartialEq, Eq, Hash)]
175pub struct RepositoryId(pub u64);
176
177impl RepositoryId {
178    pub fn new() -> Self {
179        Self(random())
180    }
181}
182
183impl Deref for RepositoryId {
184    type Target = u64;
185
186    fn deref(&self) -> &Self::Target {
187        &self.0
188    }
189}