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

ambee/giterated

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

Singular tag request

Emilia - ⁨1⁩ year ago

parent: tbd commit: ⁨d239950

Showing ⁨⁨6⁩ changed files⁩ with ⁨⁨225⁩ insertions⁩ and ⁨⁨68⁩ deletions⁩

giterated-daemon/src/backend/git/mod.rs

View file
@@ -13,7 +13,8 @@ use giterated_models::repository::{
13 13 RepositoryFileFromPathRequest, RepositoryFileInspectRequest, RepositoryIssue,
14 14 RepositoryIssueLabelsRequest, RepositoryIssuesCountRequest, RepositoryIssuesRequest,
15 15 RepositoryLastCommitOfFileRequest, RepositoryStatistics, RepositoryStatisticsRequest,
16 RepositoryTag, RepositoryTagsRequest, RepositoryTreeEntry, RepositoryVisibility, Visibility,
16 RepositoryTag, RepositoryTagRequest, RepositoryTagsRequest, RepositoryTreeEntry,
17 RepositoryVisibility, Visibility,
17 18 };
18 19
19 20 use giterated_models::user::User;
@@ -135,6 +136,8 @@ pub enum GitBackendError {
135 136 HeadNotFound,
136 137 #[error("Couldn't find default repository branch")]
137 138 DefaultNotFound,
139 #[error("Couldn't find tag with name `{0}`")]
140 TagNotFound(String),
138 141 #[error("Couldn't find path in repository `{0}`")]
139 142 PathNotFound(String),
140 143 #[error("Couldn't find branch with name `{0}`")]
@@ -155,6 +158,41 @@ pub enum GitBackendError {
155 158 FailedDiffing(String, String),
156 159 }
157 160
161 /// Parses and trims a git message (commit, tag) into a summary and body.
162 pub fn parse_trim_git_message(message: &str) -> (Option<String>, Option<String>) {
163 // Iterate over the lines
164 let mut lines = message
165 .lines()
166 .filter_map(|line| {
167 if line.is_empty() {
168 None
169 } else {
170 // Trim the whitespace for every line
171 let mut whitespace_removed = String::with_capacity(line.len());
172
173 line.split_whitespace().for_each(|word| {
174 if !whitespace_removed.is_empty() {
175 whitespace_removed.push(' ');
176 }
177
178 whitespace_removed.push_str(word);
179 });
180
181 Some(whitespace_removed)
182 }
183 })
184 .collect::<Vec<String>>();
185
186 let summary = Some(lines.remove(0));
187 let body = if lines.is_empty() {
188 None
189 } else {
190 Some(lines.join("\n"))
191 };
192
193 (summary, body)
194 }
195
158 196 pub struct GitBackend {
159 197 pg_pool: PgPool,
160 198 repository_folder: String,
@@ -648,6 +686,22 @@ impl RepositoryBackend for GitBackend {
648 686 .await
649 687 }
650 688
689 async fn repository_get_tag(
690 &mut self,
691 requester: &Option<AuthenticatedUser>,
692 repository_object: &mut Object<'_, StackOperationState, Repository, GiteratedStack>,
693 OperationState(operation_state): OperationState<StackOperationState>,
694 request: &RepositoryTagRequest,
695 ) -> Result<RepositoryTag, Error> {
696 self.handle_repository_get_tag(
697 requester,
698 repository_object,
699 OperationState(operation_state),
700 request,
701 )
702 .await
703 }
704
651 705 async fn exists(
652 706 &mut self,
653 707 requester: &Option<AuthenticatedUser>,

giterated-daemon/src/backend/git/tags.rs

View file
@@ -1,13 +1,57 @@
1 1 use anyhow::Error;
2 2 use giterated_models::{
3 3 object::Object,
4 repository::{Commit, CommitSignature, Repository, RepositoryTag, RepositoryTagsRequest},
4 repository::{
5 Commit, CommitSignature, Repository, RepositoryTag, RepositoryTagRequest,
6 RepositoryTagsRequest,
7 },
5 8 };
6 9 use giterated_stack::{AuthenticatedUser, GiteratedStack, OperationState, StackOperationState};
7 10
8 use super::GitBackend;
11 use super::{parse_trim_git_message, GitBackend, GitBackendError};
9 12
10 13 impl GitBackend {
14 /// Converts a git2 tag into our own type, couldn't implement [`From`] or impl on [`RepositoryTag`] from here.
15 pub fn git2_annotated_tag_to_own_tag(
16 id: String,
17 name: String,
18 tag: git2::Tag,
19 ) -> RepositoryTag {
20 // Get the tag message and split it into a summary and body
21 let (summary, body) = if let Some(message) = tag.message() {
22 parse_trim_git_message(message)
23 } else {
24 (None, None)
25 };
26
27 // Get the commit the tag is (possibly) pointing to
28 let commit = tag
29 .peel()
30 .map(|obj| obj.into_commit().ok())
31 .ok()
32 .flatten()
33 .map(|c| Commit::from(c));
34 // Get the author of the tag
35 let author: Option<CommitSignature> = tag.tagger().map(|s| s.into());
36 // Get the time the tag or pointed commit was created
37 let time = if let Some(ref author) = author {
38 Some(author.time)
39 } else {
40 // Get possible commit time if the tag has no author time
41 commit.as_ref().map(|c| c.time.clone())
42 };
43
44 RepositoryTag {
45 id: id.to_string(),
46 name: name.to_string(),
47 summary,
48 body,
49 author,
50 time,
51 commit,
52 }
53 }
54
11 55 /// .0: List of tags in passed range
12 56 /// .1: Total amount of tags
13 57 pub async fn handle_repository_get_tags(
@@ -31,65 +75,11 @@ impl GitBackend {
31 75
32 76 // Find the tag so we can get the messages attached if any
33 77 if let Ok(tag) = git.find_tag(id) {
34 // Get the tag message and split it into a summary and body
35 let (summary, body) = if let Some(message) = tag.message() {
36 // Iterate over the lines
37 let mut lines = message
38 .lines()
39 .map(|line| {
40 // Trim the whitespace for every line
41 let mut whitespace_removed = String::with_capacity(line.len());
42
43 line.split_whitespace().for_each(|word| {
44 if !whitespace_removed.is_empty() {
45 whitespace_removed.push(' ');
46 }
47
48 whitespace_removed.push_str(word);
49 });
50
51 whitespace_removed
52 })
53 .collect::<Vec<String>>();
54
55 let summary = Some(lines.remove(0));
56 let body = if lines.is_empty() {
57 None
58 } else {
59 Some(lines.join("\n"))
60 };
61
62 (summary, body)
63 } else {
64 (None, None)
65 };
66
67 // Get the commit the tag is (possibly) pointing to
68 let commit = tag
69 .peel()
70 .map(|obj| obj.into_commit().ok())
71 .ok()
72 .flatten()
73 .map(|c| Commit::from(c));
74 // Get the author of the tag
75 let author: Option<CommitSignature> = tag.tagger().map(|s| s.into());
76 // Get the time the tag or pointed commit was created
77 let time = if let Some(ref author) = author {
78 Some(author.time)
79 } else {
80 // Get possible commit time if the tag has no author time
81 commit.as_ref().map(|c| c.time.clone())
82 };
83
84 tags.push(RepositoryTag {
85 id: id.to_string(),
86 name: name.to_string(),
87 summary,
88 body,
89 author,
90 time,
91 commit,
92 });
78 tags.push(Self::git2_annotated_tag_to_own_tag(
79 id.to_string(),
80 name,
81 tag,
82 ));
93 83 } else {
94 84 // Lightweight commit, we try and find the commit it's pointing to
95 85 let commit = git.find_commit(id).ok().map(|c| Commit::from(c));
@@ -132,4 +122,46 @@ impl GitBackend {
132 122
133 123 Ok((tags, tag_count))
134 124 }
125
126 pub async fn handle_repository_get_tag(
127 &mut self,
128 requester: &Option<AuthenticatedUser>,
129 repository_object: &mut Object<'_, StackOperationState, Repository, GiteratedStack>,
130 OperationState(_operation_state): OperationState<StackOperationState>,
131 request: &RepositoryTagRequest,
132 ) -> Result<RepositoryTag, Error> {
133 let repository = repository_object.object();
134 let git = self
135 .open_repository_and_check_permissions(&repository.owner, &repository.name, requester)
136 .await?;
137
138 // Get the tag id by parsing the ref
139 let full_ref_name = format!("refs/tags/{}", request.name.clone());
140 let tag_id = git
141 .refname_to_id(&full_ref_name)
142 .map_err(|_| GitBackendError::RefNotFound(full_ref_name.clone()))?;
143
144 let tag = git.find_tag(tag_id);
145 if let Ok(tag) = tag {
146 // Convert the annotated tag into our own type
147 Ok(Self::git2_annotated_tag_to_own_tag(
148 tag_id.to_string(),
149 request.name.clone(),
150 tag,
151 ))
152 } else {
153 // Lightweight tag, we try and find the commit it's pointing to
154 let commit = git.find_commit(tag_id).ok().map(|c| Commit::from(c));
155
156 Ok(RepositoryTag {
157 id: tag_id.to_string(),
158 name: request.name.clone(),
159 summary: None,
160 body: None,
161 author: None,
162 time: commit.as_ref().map(|c| c.time.clone()),
163 commit,
164 })
165 }
166 }
135 167 }

giterated-daemon/src/backend/mod.rs

View file
@@ -24,12 +24,13 @@ use giterated_models::repository::{
24 24 RepositoryFileFromIdRequest, RepositoryFileFromPathRequest, RepositoryFileInspectRequest,
25 25 RepositoryIssue, RepositoryIssueLabelsRequest, RepositoryIssuesCountRequest,
26 26 RepositoryIssuesRequest, RepositoryLastCommitOfFileRequest, RepositoryStatistics,
27 RepositoryStatisticsRequest, RepositorySummary, RepositoryTag, RepositoryTagsRequest,
28 RepositoryTreeEntry,
27 RepositoryStatisticsRequest, RepositorySummary, RepositoryTag, RepositoryTagRequest,
28 RepositoryTagsRequest, RepositoryTreeEntry,
29 29 };
30 30
31 31 use giterated_models::user::User;
32 32
33 // TODO: Document all functions
33 34 #[async_trait(?Send)]
34 35 pub trait RepositoryBackend {
35 36 async fn create_repository(
@@ -121,6 +122,13 @@ pub trait RepositoryBackend {
121 122 OperationState(operation_state): OperationState<StackOperationState>,
122 123 request: &RepositoryTagsRequest,
123 124 ) -> Result<(Vec<RepositoryTag>, usize), Error>;
125 async fn repository_get_tag(
126 &mut self,
127 requester: &Option<AuthenticatedUser>,
128 repository_object: &mut Object<'_, StackOperationState, Repository, GiteratedStack>,
129 OperationState(operation_state): OperationState<StackOperationState>,
130 request: &RepositoryTagRequest,
131 ) -> Result<RepositoryTag, Error>;
124 132 async fn exists(
125 133 &mut self,
126 134 requester: &Option<AuthenticatedUser>,

giterated-daemon/src/database_backend/handler.rs

View file
@@ -12,7 +12,8 @@ use giterated_models::{
12 12 RepositoryDiffRequest, RepositoryFile, RepositoryFileFromIdRequest,
13 13 RepositoryFileFromPathRequest, RepositoryFileInspectRequest, RepositoryInfoRequest,
14 14 RepositoryLastCommitOfFileRequest, RepositoryStatistics, RepositoryStatisticsRequest,
15 RepositorySummary, RepositoryTag, RepositoryTagsRequest, RepositoryView, Visibility,
15 RepositorySummary, RepositoryTag, RepositoryTagRequest, RepositoryTagsRequest,
16 RepositoryView, Visibility,
16 17 },
17 18 user::{User, UserRepositoriesRequest},
18 19 };
@@ -229,6 +230,34 @@ pub async fn repository_get_tags(
229 230 Ok(branches)
230 231 }
231 232
233 pub async fn repository_get_tag(
234 object: Repository,
235 operation: RepositoryTagRequest,
236 state: DatabaseBackend,
237 OperationState(operation_state): OperationState<StackOperationState>,
238 backend: GiteratedStack,
239 requester: Option<AuthenticatedUser>,
240 ) -> Result<RepositoryTag, OperationError<RepositoryError>> {
241 let mut object = backend
242 .get_object::<Repository>(&object.to_string(), &operation_state)
243 .await
244 .unwrap();
245
246 let mut repository_backend = state.repository_backend.lock().await;
247 let branches = repository_backend
248 .repository_get_tag(
249 &requester,
250 &mut object,
251 OperationState(operation_state),
252 &operation,
253 )
254 .await
255 .as_internal_error()?;
256 drop(repository_backend);
257
258 Ok(branches)
259 }
260
232 261 pub async fn repository_file_from_id(
233 262 object: Repository,
234 263 operation: RepositoryFileFromIdRequest,

giterated-daemon/src/database_backend/mod.rs

View file
@@ -26,8 +26,8 @@ use self::handler::{
26 26 instance_authentication_request, instance_create_repository_request,
27 27 instance_registration_request, repository_commit_before, repository_commit_by_id,
28 28 repository_diff, repository_diff_patch, repository_file_from_id, repository_file_from_path,
29 repository_get_branch, repository_get_branches, repository_get_statistics, repository_get_tags,
30 repository_info, repository_last_commit_of_file, user_get_repositories,
29 repository_get_branch, repository_get_branches, repository_get_statistics, repository_get_tag,
30 repository_get_tags, repository_info, repository_last_commit_of_file, user_get_repositories,
31 31 };
32 32
33 33 /// A backend implementation which attempts to resolve data from the instance's database.
@@ -97,7 +97,8 @@ impl DatabaseBackend {
97 97 .operation(repository_commit_before)
98 98 .operation(repository_get_branches)
99 99 .operation(repository_get_branch)
100 .operation(repository_get_tags);
100 .operation(repository_get_tags)
101 .operation(repository_get_tag);
101 102
102 103 builder
103 104 }

giterated-models/src/repository/operations.rs

View file
@@ -325,6 +325,25 @@ impl GiteratedOperation<Repository> for RepositoryTagsRequest {
325 325 type Failure = RepositoryError;
326 326 }
327 327
328 /// A request to get a single tag by name.
329 ///
330 /// # Authentication
331 /// - Instance Authentication
332 /// - Validate request against the `issued_for` public key
333 /// - Validate User token against the user's instance's public key
334 /// # Authorization
335 /// - User Authorization
336 /// - Potential User permissions checks
337 #[derive(Clone, Debug, Serialize, Deserialize)]
338 pub struct RepositoryTagRequest {
339 pub name: String,
340 }
341
342 impl GiteratedOperation<Repository> for RepositoryTagRequest {
343 type Success = RepositoryTag;
344 type Failure = RepositoryError;
345 }
346
328 347 impl<S: Clone + Send + Sync, B: ObjectBackend<S> + std::fmt::Debug> Object<'_, S, Repository, B> {
329 348 pub async fn info(
330 349 &mut self,
@@ -494,6 +513,20 @@ impl<S: Clone + Send + Sync, B: ObjectBackend<S> + std::fmt::Debug> Object<'_, S
494 513 .await
495 514 }
496 515
516 pub async fn tag(
517 &mut self,
518 name: &str,
519 operation_state: &S,
520 ) -> Result<RepositoryTag, OperationError<RepositoryError>> {
521 self.request::<RepositoryTagRequest>(
522 RepositoryTagRequest {
523 name: name.to_string(),
524 },
525 operation_state,
526 )
527 .await
528 }
529
497 530 // pub async fn issues_count(&mut self) -> Result<u64, OperationError<RepositoryError>> {
498 531 // self.request::<RepositoryIssuesCountRequest>(RepositoryIssuesCountRequest)
499 532 // .await