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

ambee/giterated

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

Fix being able to view a repository when nothing has been committed

Emilia - ⁨1⁩ year ago

parent: tbd commit: ⁨810cd2a

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