JavaScript is disabled, refresh for a better experience. ambee/giterated

ambee/giterated

Git repository hosting, collaboration, and discovery for the Fediverse.

Fixes

Amber - ⁨2⁩ years ago

parent: tbd commit: ⁨73a5af5

⁨giterated-models/src/repository/mod.rs⁩ - ⁨7932⁩ bytes
Raw
1 use std::fmt::{Display, Formatter};
2 use std::str::FromStr;
3
4 use serde::{Deserialize, Serialize};
5
6 use crate::object::GiteratedObject;
7
8 use super::{instance::Instance, user::User};
9
10 mod operations;
11 mod settings;
12 mod values;
13
14 pub use operations::*;
15 pub use settings::*;
16 pub use values::*;
17
18 /// A repository, defined by the instance it exists on along with
19 /// its owner and name.
20 ///
21 /// # Textual Format
22 /// A repository's textual reference is defined as:
23 ///
24 /// `{owner: User}/{name: String}@{instance: Instance}`
25 ///
26 /// # Examples
27 /// For the repository named `foo` owned by `barson:giterated.dev` on the instance
28 /// `giterated.dev`, the following [`Repository`] initialization would
29 /// be valid:
30 ///
31 /// ```
32 //# use giterated_models::model::repository::Repository;
33 //# use giterated_models::model::instance::Instance;
34 //# use giterated_models::model::user::User;
35 /// let repository = Repository {
36 /// owner: User::from_str("barson:giterated.dev").unwrap(),
37 /// name: String::from("foo"),
38 /// instance: Instance::from_str("giterated.dev").unwrap()
39 /// };
40 ///
41 /// // This is correct
42 /// assert_eq!(Repository::from_str("barson:giterated.dev/[email protected]").unwrap(), repository);
43 /// ```
44 #[derive(Hash, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
45 pub struct Repository {
46 pub owner: User,
47 pub name: String,
48 /// Instance the repository is on
49 pub instance: Instance,
50 }
51
52 impl Display for Repository {
53 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
54 f.write_str(&format!("{}/{}@{}", self.owner, self.name, self.instance))
55 }
56 }
57
58 impl GiteratedObject for Repository {
59 fn object_name() -> &'static str {
60 "repository"
61 }
62
63 fn from_object_str(object_str: &str) -> Result<Self, anyhow::Error> {
64 Ok(Repository::from_str(object_str)?)
65 }
66 }
67
68 impl TryFrom<String> for Repository {
69 type Error = RepositoryParseError;
70
71 fn try_from(value: String) -> Result<Self, Self::Error> {
72 Self::from_str(&value)
73 }
74 }
75
76 impl FromStr for Repository {
77 type Err = RepositoryParseError;
78
79 fn from_str(s: &str) -> Result<Self, Self::Err> {
80 let mut by_ampersand = s.split('@');
81 let mut path_split = by_ampersand
82 .next()
83 .ok_or_else(|| RepositoryParseError)?
84 .split('/');
85
86 let instance = Instance::from_str(by_ampersand.next().ok_or_else(|| RepositoryParseError)?)
87 .map_err(|_| RepositoryParseError)?;
88 let owner = User::from_str(path_split.next().ok_or_else(|| RepositoryParseError)?)
89 .map_err(|_| RepositoryParseError)?;
90 let name = path_split
91 .next()
92 .ok_or_else(|| RepositoryParseError)?
93 .to_string();
94
95 Ok(Self {
96 instance,
97 owner,
98 name,
99 })
100 }
101 }
102
103 #[derive(Debug, thiserror::Error)]
104 #[error("no parse!")]
105 pub struct RepositoryParseError;
106
107 /// Visibility of the repository to the general eye
108 #[derive(PartialEq, Eq, Debug, Hash, Serialize, Deserialize, Clone, sqlx::Type)]
109 #[sqlx(type_name = "visibility", rename_all = "lowercase")]
110 pub enum RepositoryVisibility {
111 Public,
112 Unlisted,
113 Private,
114 }
115
116 /// Implements [`Display`] for [`RepositoryVisiblity`] using [`Debug`]
117 impl Display for RepositoryVisibility {
118 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
119 write!(f, "{:?}", self)
120 }
121 }
122
123 #[derive(Clone, Debug, Serialize, Deserialize)]
124 pub struct RepositoryView {
125 /// Name of the repository
126 ///
127 /// This is different than the [`Repository`] name,
128 /// which may be a path.
129 pub name: String,
130 /// Owner of the Repository
131 pub owner: User,
132 /// Repository description
133 pub description: Option<String>,
134 /// Repository visibility
135 pub visibility: RepositoryVisibility,
136 /// Default branch of the repository
137 pub default_branch: String,
138 /// Last commit made to the repository
139 pub latest_commit: Option<Commit>,
140 /// Revision of the displayed tree
141 pub tree_rev: Option<String>,
142 /// Repository tree
143 pub tree: Vec<RepositoryTreeEntry>,
144 }
145
146 #[derive(Debug, Clone, Serialize, Deserialize)]
147 pub enum RepositoryObjectType {
148 Tree,
149 Blob,
150 }
151
152 /// Stored info for our tree entries
153 #[derive(Debug, Clone, Serialize, Deserialize)]
154 pub struct RepositoryTreeEntry {
155 /// Name of the tree/blob
156 pub name: String,
157 /// Type of the tree entry
158 pub object_type: RepositoryObjectType,
159 /// Git supplies us with the mode at all times, and people like it displayed.
160 pub mode: i32,
161 /// File size
162 pub size: Option<usize>,
163 /// Last commit made to the tree/blob
164 pub last_commit: Option<Commit>,
165 }
166
167 impl RepositoryTreeEntry {
168 // I love you Emilia <3
169 pub fn new(name: &str, object_type: RepositoryObjectType, mode: i32) -> Self {
170 Self {
171 name: name.to_string(),
172 object_type,
173 mode,
174 size: None,
175 last_commit: None,
176 }
177 }
178 }
179
180 #[derive(Debug, Clone, Serialize, Deserialize)]
181 pub struct RepositoryTreeEntryWithCommit {
182 pub tree_entry: RepositoryTreeEntry,
183 pub commit: Commit,
184 }
185
186 /// Info about a git commit
187 #[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
188 pub struct Commit {
189 /// Unique commit ID
190 pub oid: String,
191 /// Shortened abbreviated OID
192 /// This starts at the git config's "core.abbrev" length (default 7 characters) and
193 /// iteratively extends to a longer string if that length is ambiguous. The
194 /// result will be unambiguous (at least until new objects are added to the repository).
195 pub short_oid: String,
196 /// Full commit message
197 pub message: Option<String>,
198 /// Who created the commit
199 pub author: CommitSignature,
200 /// Who committed the commit
201 pub committer: CommitSignature,
202 /// Time when the commit happened
203 pub time: chrono::NaiveDateTime,
204 }
205
206 /// Gets all info from [`git2::Commit`] for easy use
207 impl From<git2::Commit<'_>> for Commit {
208 fn from(commit: git2::Commit<'_>) -> Self {
209 Self {
210 oid: commit.id().to_string(),
211 // This shouldn't ever fail, as we already know the object has an oid.
212 short_oid: commit
213 .as_object()
214 .short_id()
215 .unwrap()
216 .as_str()
217 .unwrap()
218 .to_string(),
219 message: commit.message().map(|message| message.to_string()),
220 author: commit.author().into(),
221 committer: commit.committer().into(),
222 time: chrono::NaiveDateTime::from_timestamp_opt(commit.time().seconds(), 0).unwrap(),
223 }
224 }
225 }
226
227 /// Git commit signature
228 #[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
229 pub struct CommitSignature {
230 pub name: Option<String>,
231 pub email: Option<String>,
232 pub time: chrono::NaiveDateTime,
233 }
234
235 /// Converts the signature from git2 into something usable without explicit lifetimes.
236 impl From<git2::Signature<'_>> for CommitSignature {
237 fn from(signature: git2::Signature<'_>) -> Self {
238 Self {
239 name: signature.name().map(|name| name.to_string()),
240 email: signature.email().map(|email| email.to_string()),
241 time: chrono::NaiveDateTime::from_timestamp_opt(signature.when().seconds(), 0).unwrap(),
242 }
243 }
244 }
245
246 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
247 pub struct RepositorySummary {
248 pub repository: Repository,
249 pub owner: User,
250 pub visibility: RepositoryVisibility,
251 pub description: Option<String>,
252 pub last_commit: Option<Commit>,
253 }
254
255 #[derive(Clone, Debug, Serialize, Deserialize)]
256 pub struct IssueLabel {
257 pub name: String,
258 pub color: String,
259 }
260
261 #[derive(Clone, Debug, Serialize, Deserialize)]
262 pub struct RepositoryIssue {
263 pub author: User,
264 pub id: u64,
265 pub title: String,
266 pub contents: String,
267 pub labels: Vec<IssueLabel>,
268 }
269