Singular tag request
parent: tbd commit: d239950
Showing 6 changed files with 225 insertions and 68 deletions
giterated-daemon/src/backend/git/mod.rs
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 |