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

ambee/giterated

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

So. Much. Work.

Amber - ⁨2⁩ years ago

parent: tbd commit: ⁨b05f964

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