gitscope
← rgit / 0.1.1 / src/database/schema/commit.rs
https://dev.kronoz.win/r/rgit.git
src/database/schema/commit.rs · Rust
Blame Raw
  1use std::{borrow::Cow, ops::Deref};
  2
  3use git2::{Oid, Signature};
  4use serde::{Deserialize, Deserializer, Serialize, Serializer};
  5use sled::IVec;
  6use time::OffsetDateTime;
  7use yoke::{Yoke, Yokeable};
  8
  9use crate::database::schema::Yoked;
 10
 11#[derive(Serialize, Deserialize, Debug, Yokeable)]
 12pub struct Commit<'a> {
 13    #[serde(borrow)]
 14    pub summary: Cow<'a, str>,
 15    #[serde(borrow)]
 16    pub message: Cow<'a, str>,
 17    pub author: Author<'a>,
 18    pub committer: Author<'a>,
 19    pub hash: CommitHash<'a>,
 20}
 21
 22impl<'a> Commit<'a> {
 23    pub fn new(
 24        commit: &'a git2::Commit<'_>,
 25        author: &'a git2::Signature<'_>,
 26        committer: &'a git2::Signature<'_>,
 27    ) -> Self {
 28        Self {
 29            summary: commit
 30                .summary_bytes()
 31                .map_or(Cow::Borrowed(""), String::from_utf8_lossy),
 32            message: commit
 33                .body_bytes()
 34                .map_or(Cow::Borrowed(""), String::from_utf8_lossy),
 35            committer: committer.into(),
 36            author: author.into(),
 37            hash: CommitHash::Oid(commit.id()),
 38        }
 39    }
 40
 41    pub fn insert(&self, batch: &CommitTree, id: usize) {
 42        batch
 43            .insert(id.to_be_bytes(), bincode::serialize(self).unwrap())
 44            .unwrap();
 45    }
 46}
 47
 48#[derive(Debug)]
 49pub enum CommitHash<'a> {
 50    Oid(Oid),
 51    Bytes(&'a [u8]),
 52}
 53
 54impl<'a> Deref for CommitHash<'a> {
 55    type Target = [u8];
 56
 57    fn deref(&self) -> &Self::Target {
 58        match self {
 59            CommitHash::Oid(v) => v.as_bytes(),
 60            CommitHash::Bytes(v) => v,
 61        }
 62    }
 63}
 64
 65impl Serialize for CommitHash<'_> {
 66    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
 67    where
 68        S: Serializer,
 69    {
 70        match self {
 71            CommitHash::Oid(v) => v.as_bytes().serialize(serializer),
 72            CommitHash::Bytes(v) => v.serialize(serializer),
 73        }
 74    }
 75}
 76
 77impl<'a, 'de: 'a> Deserialize<'de> for CommitHash<'a> {
 78    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
 79    where
 80        D: Deserializer<'de>,
 81    {
 82        let bytes = <&'a [u8]>::deserialize(deserializer)?;
 83        Ok(Self::Bytes(bytes))
 84    }
 85}
 86
 87#[derive(Serialize, Deserialize, Debug)]
 88pub struct Author<'a> {
 89    #[serde(borrow)]
 90    pub name: Cow<'a, str>,
 91    #[serde(borrow)]
 92    pub email: Cow<'a, str>,
 93    pub time: OffsetDateTime,
 94}
 95
 96impl<'a> From<&'a git2::Signature<'_>> for Author<'a> {
 97    fn from(author: &'a Signature<'_>) -> Self {
 98        Self {
 99            name: String::from_utf8_lossy(author.name_bytes()),
100            email: String::from_utf8_lossy(author.email_bytes()),
101            // TODO: this needs to deal with offset
102            time: OffsetDateTime::from_unix_timestamp(author.when().seconds()).unwrap(),
103        }
104    }
105}
106
107pub struct CommitTree(sled::Tree);
108
109impl Deref for CommitTree {
110    type Target = sled::Tree;
111
112    fn deref(&self) -> &Self::Target {
113        &self.0
114    }
115}
116
117pub type YokedCommit = Yoked<Commit<'static>>;
118
119impl CommitTree {
120    pub(super) fn new(tree: sled::Tree) -> Self {
121        Self(tree)
122    }
123
124    pub fn fetch_latest_one(&self) -> Option<YokedCommit> {
125        self.last().unwrap().map(|(_, value)| {
126            // internally value is an Arc so it should already be stablederef but because
127            // of reasons unbeknownst to me, sled has its own Arc implementation so we need
128            // to box the value as well to get a stablederef...
129            let value = Box::new(value);
130
131            Yoke::try_attach_to_cart(value, |data: &IVec| bincode::deserialize(data)).unwrap()
132        })
133    }
134
135    pub async fn fetch_latest(&self, amount: usize, offset: usize) -> Vec<YokedCommit> {
136        let latest_key = if let Some((latest_key, _)) = self.last().unwrap() {
137            let mut latest_key_bytes = [0; std::mem::size_of::<usize>()];
138            latest_key_bytes.copy_from_slice(&latest_key);
139            usize::from_be_bytes(latest_key_bytes)
140        } else {
141            return vec![];
142        };
143
144        let end = latest_key.saturating_sub(offset);
145        let start = end.saturating_sub(amount - 1);
146        let iter = self.range(start.to_be_bytes()..=end.to_be_bytes());
147
148        tokio::task::spawn_blocking(move || {
149            iter.rev()
150                .map(|res| {
151                    let (_, value) = res?;
152
153                    // internally value is an Arc so it should already be stablederef but because
154                    // of reasons unbeknownst to me, sled has its own Arc implementation so we need
155                    // to box the value as well to get a stablederef...
156                    let value = Box::new(value);
157
158                    Ok(
159                        Yoke::try_attach_to_cart(value, |data: &IVec| bincode::deserialize(data))
160                            .unwrap(),
161                    )
162                })
163                .collect::<Result<Vec<_>, sled::Error>>()
164                .unwrap()
165        })
166        .await
167        .unwrap()
168    }
169}