gitscope
← rgit / 0.1.4 / src/database/schema/commit.rs
https://dev.kronoz.win/r/rgit.git
src/database/schema/commit.rs · Rust
Blame Raw
  1use std::sync::Arc;
  2
  3use anyhow::Context;
  4use gix::{actor::SignatureRef, ObjectId};
  5use rkyv::{Archive, Serialize};
  6use rocksdb::{IteratorMode, ReadOptions, WriteBatch};
  7use time::{OffsetDateTime, UtcOffset};
  8use tracing::debug;
  9use yoke::{Yoke, Yokeable};
 10
 11use crate::database::schema::{
 12    prefixes::{COMMIT_COUNT_FAMILY, COMMIT_FAMILY},
 13    repository::RepositoryId,
 14    Yoked,
 15};
 16
 17#[derive(Serialize, Archive, Debug, Yokeable)]
 18pub struct Commit {
 19    pub summary: String,
 20    pub message: String,
 21    pub author: Author,
 22    pub committer: Author,
 23    pub hash: [u8; 20],
 24}
 25
 26impl Commit {
 27    pub fn new(
 28        commit: &gix::Commit<'_>,
 29        author: SignatureRef<'_>,
 30        committer: SignatureRef<'_>,
 31    ) -> Result<Self, anyhow::Error> {
 32        let message = commit.message()?;
 33
 34        Ok(Self {
 35            summary: message.summary().to_string(),
 36            message: message.body.map(ToString::to_string).unwrap_or_default(),
 37            committer: committer.try_into()?,
 38            author: author.try_into()?,
 39            hash: match commit.id().detach() {
 40                ObjectId::Sha1(d) => d,
 41            },
 42        })
 43    }
 44
 45    pub fn insert(&self, tree: &CommitTree, id: u64, tx: &mut WriteBatch) -> anyhow::Result<()> {
 46        tree.insert(id, self, tx)
 47    }
 48}
 49
 50#[derive(Serialize, Archive, Debug)]
 51pub struct Author {
 52    pub name: String,
 53    pub email: String,
 54    pub time: (i64, i32),
 55}
 56
 57impl ArchivedAuthor {
 58    pub fn time(&self) -> OffsetDateTime {
 59        OffsetDateTime::from_unix_timestamp(self.time.0.to_native())
 60            .unwrap()
 61            .to_offset(UtcOffset::from_whole_seconds(self.time.1.to_native()).unwrap())
 62    }
 63}
 64
 65impl TryFrom<SignatureRef<'_>> for Author {
 66    type Error = anyhow::Error;
 67
 68    fn try_from(author: SignatureRef<'_>) -> Result<Self, anyhow::Error> {
 69        Ok(Self {
 70            name: author.name.to_string(),
 71            email: author.email.to_string(),
 72            time: (author.time.seconds, author.time.offset),
 73        })
 74    }
 75}
 76
 77pub struct CommitTree {
 78    db: Arc<rocksdb::DB>,
 79    pub prefix: Box<[u8]>,
 80}
 81
 82pub type YokedCommit = Yoked<&'static <Commit as Archive>::Archived>;
 83
 84impl CommitTree {
 85    pub(super) fn new(db: Arc<rocksdb::DB>, repository: RepositoryId, reference: &str) -> Self {
 86        let mut prefix = Vec::with_capacity(std::mem::size_of::<u64>() + reference.len() + 1);
 87        prefix.extend_from_slice(&repository.to_be_bytes());
 88        prefix.extend_from_slice(reference.as_bytes());
 89        prefix.push(b'\0');
 90
 91        Self {
 92            db,
 93            prefix: prefix.into_boxed_slice(),
 94        }
 95    }
 96
 97    pub fn drop_commits(&self) -> anyhow::Result<()> {
 98        let mut to = self.prefix.clone();
 99        *to.last_mut().unwrap() += 1;
100
101        let commit_cf = self
102            .db
103            .cf_handle(COMMIT_FAMILY)
104            .context("commit column family missing")?;
105        self.db.delete_range_cf(commit_cf, &self.prefix, &to)?;
106
107        let commit_count_cf = self
108            .db
109            .cf_handle(COMMIT_COUNT_FAMILY)
110            .context("missing column family")?;
111        self.db.delete_cf(commit_count_cf, &self.prefix)?;
112
113        Ok(())
114    }
115
116    pub fn update_counter(&self, count: u64, tx: &mut WriteBatch) -> anyhow::Result<()> {
117        let cf = self
118            .db
119            .cf_handle(COMMIT_COUNT_FAMILY)
120            .context("missing column family")?;
121
122        tx.put_cf(cf, &self.prefix, count.to_be_bytes());
123
124        Ok(())
125    }
126
127    pub fn len(&self) -> anyhow::Result<u64> {
128        let cf = self
129            .db
130            .cf_handle(COMMIT_COUNT_FAMILY)
131            .context("missing column family")?;
132
133        let Some(res) = self.db.get_pinned_cf(cf, &self.prefix)? else {
134            return Ok(0);
135        };
136
137        let out: [u8; std::mem::size_of::<u64>()] = res.as_ref().try_into()?;
138        Ok(u64::from_be_bytes(out))
139    }
140
141    fn insert(&self, id: u64, commit: &Commit, tx: &mut WriteBatch) -> anyhow::Result<()> {
142        let cf = self
143            .db
144            .cf_handle(COMMIT_FAMILY)
145            .context("missing column family")?;
146
147        let mut key = self.prefix.to_vec();
148        key.extend_from_slice(&id.to_be_bytes());
149
150        tx.put_cf(cf, key, rkyv::to_bytes::<rkyv::rancor::Error>(commit)?);
151
152        Ok(())
153    }
154
155    pub fn fetch_latest_one(&self) -> Result<Option<YokedCommit>, anyhow::Error> {
156        let mut key = self.prefix.to_vec();
157        key.extend_from_slice(&(self.len()?.saturating_sub(1)).to_be_bytes());
158
159        let cf = self
160            .db
161            .cf_handle(COMMIT_FAMILY)
162            .context("missing column family")?;
163
164        let Some(value) = self.db.get_cf(cf, key)? else {
165            return Ok(None);
166        };
167
168        Yoke::try_attach_to_cart(Box::from(value), |value| {
169            rkyv::access::<_, rkyv::rancor::Error>(value)
170        })
171        .context("Failed to deserialize commit")
172        .map(Some)
173    }
174
175    pub fn fetch_latest(
176        &self,
177        amount: u64,
178        offset: u64,
179    ) -> Result<Vec<YokedCommit>, anyhow::Error> {
180        let cf = self
181            .db
182            .cf_handle(COMMIT_FAMILY)
183            .context("missing column family")?;
184
185        let latest_commit_id = self.len()?;
186        debug!("Searching from latest commit {latest_commit_id}");
187
188        let mut start_key = self.prefix.to_vec();
189        start_key.extend_from_slice(
190            &latest_commit_id
191                .saturating_sub(offset)
192                .saturating_sub(amount)
193                .to_be_bytes(),
194        );
195
196        let mut end_key = self.prefix.to_vec();
197        end_key.extend_from_slice(&(latest_commit_id.saturating_sub(offset)).to_be_bytes());
198
199        let mut opts = ReadOptions::default();
200        opts.set_iterate_range(start_key.as_slice()..end_key.as_slice());
201
202        opts.set_prefix_same_as_start(true);
203
204        self.db
205            .iterator_cf_opt(cf, opts, IteratorMode::End)
206            .map(|v| {
207                Yoke::try_attach_to_cart(v.context("failed to read commit")?.1, |data| {
208                    rkyv::access::<_, rkyv::rancor::Error>(data).context("failed to deserialize")
209                })
210            })
211            .collect::<Result<Vec<_>, anyhow::Error>>()
212    }
213}