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