1use std::{borrow::Cow, collections::BTreeMap, ops::Deref, path::Path};
2
3use anyhow::{Context, Result};
4use nom::AsBytes;
5use serde::{Deserialize, Serialize};
6use sled::IVec;
7use time::OffsetDateTime;
8use yoke::{Yoke, Yokeable};
9
10use crate::database::schema::{commit::CommitTree, prefixes::TreePrefix, tag::TagTree, Yoked};
11
12#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Yokeable)]
13pub struct Repository<'a> {
14 /// The ID of the repository, as stored in `sled`
15 pub id: RepositoryId,
16 /// The "clean name" of the repository (ie. `hello-world.git`)
17 #[serde(borrow)]
18 pub name: Cow<'a, str>,
19 /// The description of the repository, as it is stored in the `description` file in the
20 /// bare repo root
21 #[serde(borrow)]
22 pub description: Option<Cow<'a, str>>,
23 /// The owner of the repository (`gitweb.owner` in the repository configuration)
24 #[serde(borrow)]
25 pub owner: Option<Cow<'a, str>>,
26 /// The last time this repository was updated, currently read from the directory mtime
27 pub last_modified: OffsetDateTime,
28}
29
30pub type YokedRepository = Yoked<Repository<'static>>;
31
32impl Repository<'_> {
33 pub fn fetch_all(database: &sled::Db) -> Result<BTreeMap<String, YokedRepository>> {
34 database
35 .scan_prefix([TreePrefix::Repository as u8])
36 .filter_map(Result::ok)
37 .map(|(key, value)| {
38 // strip the prefix we've just scanned for
39 let key = String::from_utf8_lossy(&key[1..]).to_string();
40
41 // internally value is an Arc so it should already be stablederef but because
42 // of reasons unbeknownst to me, sled has its own Arc implementation so we need
43 // to box the value as well to get a stablederef...
44 let value = Box::new(value);
45
46 let value =
47 Yoke::try_attach_to_cart(value, |data: &IVec| bincode::deserialize(data))?;
48
49 Ok((key, value))
50 })
51 .collect()
52 }
53
54 pub fn insert<P: AsRef<Path>>(&self, database: &sled::Db, path: P) {
55 database
56 .insert(
57 TreePrefix::repository_id(path),
58 bincode::serialize(self).unwrap(),
59 )
60 .unwrap();
61 }
62
63 pub fn open<P: AsRef<Path>>(database: &sled::Db, path: P) -> Result<Option<YokedRepository>> {
64 database
65 .get(TreePrefix::repository_id(path))
66 .context("Failed to open indexed repository")?
67 .map(|value| {
68 // internally value is an Arc so it should already be stablederef but because
69 // of reasons unbeknownst to me, sled has its own Arc implementation so we need
70 // to box the value as well to get a stablederef...
71 let value = Box::new(value);
72
73 Yoke::try_attach_to_cart(value, |data: &IVec| bincode::deserialize(data))
74 .context("Failed to deserialise indexed repository")
75 })
76 .transpose()
77 }
78
79 pub fn commit_tree(&self, database: &sled::Db, reference: &str) -> Result<CommitTree> {
80 let tree = database
81 .open_tree(TreePrefix::commit_id(self.id, reference))
82 .context("Failed to open commit tree")?;
83
84 Ok(CommitTree::new(tree))
85 }
86
87 pub fn tag_tree(&self, database: &sled::Db) -> Result<TagTree> {
88 let tree = database
89 .open_tree(TreePrefix::tag_id(self.id))
90 .context("Failed to open tag tree")?;
91
92 Ok(TagTree::new(tree))
93 }
94
95 pub fn heads(&self, database: &sled::Db) -> Vec<String> {
96 let prefix = TreePrefix::commit_id(self.id, "");
97
98 database
99 .tree_names()
100 .into_iter()
101 .filter_map(|v| {
102 v.strip_prefix(prefix.as_bytes())
103 .map(|v| String::from_utf8_lossy(v).into_owned())
104 })
105 .collect()
106 }
107}
108
109#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Hash)]
110pub struct RepositoryId(pub(super) u64);
111
112impl RepositoryId {
113 pub fn new(db: &sled::Db) -> Self {
114 Self(db.generate_id().unwrap())
115 }
116}
117
118impl Deref for RepositoryId {
119 type Target = u64;
120
121 fn deref(&self) -> &Self::Target {
122 &self.0
123 }
124}