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