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}