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

ambee/giterated

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

More restructuring

Amber - ⁨1⁩ year ago

parent: tbd commit: ⁨10b7b7c

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