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

ambee/giterated

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

Add info to RepositoryBranch, fix unwrap and reduce duplicate code

erremilia - ⁨2⁩ years ago

parent: tbd commit: ⁨b285b99

⁨giterated-models/src/repository/mod.rs⁩ - ⁨13661⁩ 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.next().ok_or(RepositoryParseError)?.split('/');
82
83 let instance = Instance::from_str(by_ampersand.next().ok_or(RepositoryParseError)?)
84 .map_err(|_| RepositoryParseError)?;
85 let owner = User::from_str(path_split.next().ok_or(RepositoryParseError)?)
86 .map_err(|_| RepositoryParseError)?;
87 let name = path_split.next().ok_or(RepositoryParseError)?.to_string();
88
89 Ok(Self {
90 instance,
91 owner,
92 name,
93 })
94 }
95 }
96
97 #[derive(Debug, thiserror::Error)]
98 #[error("no parse!")]
99 pub struct RepositoryParseError;
100
101 /// Visibility of the repository to the general eye
102 #[derive(PartialEq, Eq, Debug, Hash, Serialize, Deserialize, Clone, sqlx::Type)]
103 #[sqlx(type_name = "visibility", rename_all = "lowercase")]
104 pub enum RepositoryVisibility {
105 Public,
106 Unlisted,
107 Private,
108 }
109
110 /// Implements [`Display`] for [`RepositoryVisiblity`] using [`Debug`]
111 impl Display for RepositoryVisibility {
112 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
113 write!(f, "{:?}", self)
114 }
115 }
116
117 #[derive(Clone, Debug, Serialize, Deserialize)]
118 pub struct RepositoryView {
119 /// Name of the repository
120 ///
121 /// This is different than the [`Repository`] name,
122 /// which may be a path.
123 pub name: String,
124 /// Owner of the Repository
125 pub owner: User,
126 /// Repository description
127 pub description: Option<Description>,
128 /// Repository visibility
129 pub visibility: Visibility,
130 /// Default branch of the repository
131 pub default_branch: DefaultBranch,
132 /// Last commit made to the repository
133 pub latest_commit: Option<LatestCommit>,
134 /// Repository statistics
135 pub stats: RepositoryStatistics,
136 /// Revision of the displayed tree
137 pub tree_rev: Option<String>,
138 /// Repository tree
139 pub tree: Vec<RepositoryTreeEntry>,
140 }
141
142 /// Generic repository statistics
143 #[derive(Clone, Debug, Serialize, Deserialize)]
144 pub struct RepositoryStatistics {
145 /// Amount of commits made to this branch in the repository
146 pub commits: usize,
147 /// Amount of branches the repository has
148 pub branches: usize,
149 /// Amount of tags the repository has
150 pub tags: usize,
151 }
152
153 /// Repository branch
154 #[derive(Clone, Debug, Serialize, Deserialize)]
155 pub struct RepositoryBranch {
156 /// Full reference name
157 pub name: String,
158 /// Whether the repository is stale or not
159 pub stale: bool,
160 /// The last commit made to the branch
161 pub last_commit: Option<Commit>,
162 }
163
164 #[derive(Clone, Debug, Serialize, Deserialize)]
165 pub struct RepositoryFile {
166 /// ID of the file
167 pub id: String,
168 /// Content of the file
169 pub content: Vec<u8>,
170 /// If the file is binary or not
171 pub binary: bool,
172 /// File size in bytes
173 pub size: usize,
174 }
175
176 #[derive(Clone, Debug, Serialize, Deserialize)]
177 pub struct RepositoryDiff {
178 /// "to" side of the diff commit
179 pub new_commit: Commit,
180 /// Total number of files changed
181 pub files_changed: usize,
182 /// Total number of insertions
183 pub insertions: usize,
184 /// Total number of deletions
185 pub deletions: usize,
186 /// List of changed files
187 pub files: Vec<RepositoryDiffFile>,
188 }
189
190 /// Represents the type of change made to a [`RepositoryDiffFile`]
191 #[derive(Clone, Debug, Serialize, Deserialize)]
192 pub enum RepositoryDiffFileStatus {
193 /// No changes
194 Unmodified,
195 Added,
196 Deleted,
197 /// Content changed between old and new
198 Modified,
199 /// Renamed between old and new
200 Renamed,
201 /// Copied from another old entry
202 Copied,
203 /// Ignored item in workdir
204 Ignored,
205 /// Untracked item in workdir
206 Untracked,
207 /// Type of file changed between old and new
208 Typechange,
209 /// File is unreadable
210 Unreadable,
211 /// File in the index is conflicted
212 Conflicted,
213 }
214
215 impl From<git2::Delta> for RepositoryDiffFileStatus {
216 fn from(status: git2::Delta) -> Self {
217 match status {
218 git2::Delta::Unmodified => Self::Unmodified,
219 git2::Delta::Added => Self::Added,
220 git2::Delta::Deleted => Self::Deleted,
221 git2::Delta::Modified => Self::Modified,
222 git2::Delta::Renamed => Self::Renamed,
223 git2::Delta::Copied => Self::Copied,
224 git2::Delta::Ignored => Self::Ignored,
225 git2::Delta::Untracked => Self::Untracked,
226 git2::Delta::Typechange => Self::Typechange,
227 git2::Delta::Unreadable => Self::Unreadable,
228 git2::Delta::Conflicted => Self::Conflicted,
229 }
230 }
231 }
232
233 /// Represents a single file of a diff
234 #[derive(Clone, Debug, Serialize, Deserialize)]
235 pub struct RepositoryDiffFile {
236 /// The type of change made to this file
237 pub status: RepositoryDiffFileStatus,
238 /// "From" side of the diff, can be nonexistent if file for example got added for the first time
239 pub old_file_info: Option<RepositoryDiffFileInfo>,
240 /// "To" side of the diff, can be nonexistent if file got removed
241 pub new_file_info: Option<RepositoryDiffFileInfo>,
242 /// Individual chunks of changes in this file
243 pub chunks: Vec<RepositoryDiffFileChunk>,
244 }
245
246 /// Represents one side of a file diff [`RepositoryDiffFile`]
247 #[derive(Clone, Debug, Serialize, Deserialize)]
248 pub struct RepositoryDiffFileInfo {
249 /// ID of the file
250 pub id: String,
251 /// Path of the entry relative to the working directory of the repository
252 pub path: String,
253 /// Size in bytes
254 pub size: u64,
255 /// If the file is binary or not
256 pub binary: bool,
257 }
258
259 /// Represents a single chunk of a file diff [`RepositoryDiffFile`]
260 #[derive(Clone, Debug, Serialize, Deserialize)]
261 pub struct RepositoryDiffFileChunk {
262 /// Header of the chunk
263 pub header: Option<String>,
264 /// Starting line number of the old file
265 pub old_start: u32,
266 /// Number of lines in "from" side of this chunk
267 pub old_lines: u32,
268 /// Starting line number of the new file
269 pub new_start: u32,
270 /// Number of lines in "to" side of this chunk
271 pub new_lines: u32,
272 /// Lines of the chunk
273 pub lines: Vec<RepositoryChunkLine>,
274 }
275
276 /// Represents the change type of the [`RepositoryChunkLine`], incomplete of what git actually provides.
277 #[derive(Clone, Debug, Serialize, Deserialize)]
278 pub enum RepositoryChunkLineType {
279 Context,
280 Addition,
281 Deletion,
282 }
283
284 impl From<git2::DiffLineType> for RepositoryChunkLineType {
285 fn from(line_type: git2::DiffLineType) -> Self {
286 match line_type {
287 git2::DiffLineType::Context => Self::Context,
288 git2::DiffLineType::Addition => Self::Addition,
289 git2::DiffLineType::Deletion => Self::Deletion,
290 _ => Self::Context,
291 }
292 }
293 }
294
295 /// Represents a single line of a [`RepositoryDiffFileChunk`]
296 #[derive(Clone, Debug, Serialize, Deserialize)]
297 pub struct RepositoryChunkLine {
298 /// Type of change the line is
299 pub change_type: RepositoryChunkLineType,
300 /// Content of the line
301 pub content: String,
302 /// Line number in old file
303 pub old_line_num: Option<u32>,
304 /// Line number in new file
305 pub new_line_num: Option<u32>,
306 }
307
308 #[derive(Debug, Clone, Serialize, Deserialize)]
309 pub enum RepositoryObjectType {
310 Tree,
311 Blob,
312 }
313
314 /// Stored info for our tree entries
315 #[derive(Debug, Clone, Serialize, Deserialize)]
316 pub struct RepositoryTreeEntry {
317 /// ID of the tree/blob
318 pub id: String,
319 /// Name of the tree/blob
320 pub name: String,
321 /// Type of the tree entry
322 pub object_type: RepositoryObjectType,
323 /// Git supplies us with the mode at all times, and people like it displayed.
324 pub mode: i32,
325 /// File size
326 pub size: Option<usize>,
327 /// Last commit made to the tree/blob
328 pub last_commit: Option<Commit>,
329 }
330
331 impl RepositoryTreeEntry {
332 // I love you Emilia <3
333 pub fn new(id: &str, name: &str, object_type: RepositoryObjectType, mode: i32) -> Self {
334 Self {
335 id: id.to_string(),
336 name: name.to_string(),
337 object_type,
338 mode,
339 size: None,
340 last_commit: None,
341 }
342 }
343 }
344
345 #[derive(Debug, Clone, Serialize, Deserialize)]
346 pub struct RepositoryTreeEntryWithCommit {
347 pub tree_entry: RepositoryTreeEntry,
348 pub commit: Commit,
349 }
350
351 /// Info about a git commit
352 #[derive(PartialEq, Hash, Eq, Debug, Clone, Serialize, Deserialize)]
353 pub struct Commit {
354 /// Unique commit ID
355 pub oid: String,
356 /// Shortened abbreviated OID
357 /// This starts at the git config's "core.abbrev" length (default 7 characters) and
358 /// iteratively extends to a longer string if that length is ambiguous. The
359 /// result will be unambiguous (at least until new objects are added to the repository).
360 pub short_oid: String,
361 /// First paragraph of the full message
362 pub summary: Option<String>,
363 /// Everything in the full message apart from the first paragraph
364 pub body: Option<String>,
365 /// All commit id's of the parents of this commit
366 pub parents: Vec<String>,
367 /// Who created the commit
368 pub author: CommitSignature,
369 /// Who committed the commit
370 pub committer: CommitSignature,
371 /// Time when the commit happened
372 pub time: chrono::NaiveDateTime,
373 }
374
375 /// Gets all info from [`git2::Commit`] for easy use
376 impl From<git2::Commit<'_>> for Commit {
377 fn from(commit: git2::Commit<'_>) -> Self {
378 Self {
379 oid: commit.id().to_string(),
380 // This shouldn't ever fail, as we already know the object has an oid.
381 short_oid: commit
382 .as_object()
383 .short_id()
384 .unwrap()
385 .as_str()
386 .unwrap()
387 .to_string(),
388 summary: commit.summary().map(|summary| summary.to_string()),
389 body: commit.body().map(|body| body.to_string()),
390 parents: commit
391 .parents()
392 .map(|parent| parent.id().to_string())
393 .collect::<Vec<String>>(),
394 author: commit.author().into(),
395 committer: commit.committer().into(),
396 time: chrono::NaiveDateTime::from_timestamp_opt(commit.time().seconds(), 0).unwrap(),
397 }
398 }
399 }
400
401 /// Git commit signature
402 #[derive(PartialEq, Hash, Eq, Debug, Clone, Serialize, Deserialize)]
403 pub struct CommitSignature {
404 pub name: Option<String>,
405 pub email: Option<String>,
406 pub time: chrono::NaiveDateTime,
407 }
408
409 /// Converts the signature from git2 into something usable without explicit lifetimes.
410 impl From<git2::Signature<'_>> for CommitSignature {
411 fn from(signature: git2::Signature<'_>) -> Self {
412 Self {
413 name: signature.name().map(|name| name.to_string()),
414 email: signature.email().map(|email| email.to_string()),
415 time: chrono::NaiveDateTime::from_timestamp_opt(signature.when().seconds(), 0).unwrap(),
416 }
417 }
418 }
419
420 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
421 pub struct RepositorySummary {
422 pub repository: Repository,
423 pub owner: User,
424 pub visibility: RepositoryVisibility,
425 pub description: Option<String>,
426 pub last_commit: Option<Commit>,
427 }
428
429 #[derive(Clone, Debug, Serialize, Deserialize)]
430 pub struct IssueLabel {
431 pub name: String,
432 pub color: String,
433 }
434
435 #[derive(Clone, Debug, Serialize, Deserialize)]
436 pub struct RepositoryIssue {
437 pub author: User,
438 pub id: u64,
439 pub title: String,
440 pub contents: String,
441 pub labels: Vec<IssueLabel>,
442 }
443