diff --git a/giterated-daemon/src/backend/git/mod.rs b/giterated-daemon/src/backend/git/mod.rs index 88331aa..4de150f 100644 --- a/giterated-daemon/src/backend/git/mod.rs +++ b/giterated-daemon/src/backend/git/mod.rs @@ -13,7 +13,8 @@ use giterated_models::repository::{ RepositoryFileFromPathRequest, RepositoryFileInspectRequest, RepositoryIssue, RepositoryIssueLabelsRequest, RepositoryIssuesCountRequest, RepositoryIssuesRequest, RepositoryLastCommitOfFileRequest, RepositoryStatistics, RepositoryStatisticsRequest, - RepositoryTag, RepositoryTagsRequest, RepositoryTreeEntry, RepositoryVisibility, Visibility, + RepositoryTag, RepositoryTagRequest, RepositoryTagsRequest, RepositoryTreeEntry, + RepositoryVisibility, Visibility, }; use giterated_models::user::User; @@ -135,6 +136,8 @@ pub enum GitBackendError { HeadNotFound, #[error("Couldn't find default repository branch")] DefaultNotFound, + #[error("Couldn't find tag with name `{0}`")] + TagNotFound(String), #[error("Couldn't find path in repository `{0}`")] PathNotFound(String), #[error("Couldn't find branch with name `{0}`")] @@ -155,6 +158,41 @@ pub enum GitBackendError { FailedDiffing(String, String), } +/// Parses and trims a git message (commit, tag) into a summary and body. +pub fn parse_trim_git_message(message: &str) -> (Option, Option) { + // Iterate over the lines + let mut lines = message + .lines() + .filter_map(|line| { + if line.is_empty() { + None + } else { + // Trim the whitespace for every line + let mut whitespace_removed = String::with_capacity(line.len()); + + line.split_whitespace().for_each(|word| { + if !whitespace_removed.is_empty() { + whitespace_removed.push(' '); + } + + whitespace_removed.push_str(word); + }); + + Some(whitespace_removed) + } + }) + .collect::>(); + + let summary = Some(lines.remove(0)); + let body = if lines.is_empty() { + None + } else { + Some(lines.join("\n")) + }; + + (summary, body) +} + pub struct GitBackend { pg_pool: PgPool, repository_folder: String, @@ -648,6 +686,22 @@ impl RepositoryBackend for GitBackend { .await } + async fn repository_get_tag( + &mut self, + requester: &Option, + repository_object: &mut Object<'_, StackOperationState, Repository, GiteratedStack>, + OperationState(operation_state): OperationState, + request: &RepositoryTagRequest, + ) -> Result { + self.handle_repository_get_tag( + requester, + repository_object, + OperationState(operation_state), + request, + ) + .await + } + async fn exists( &mut self, requester: &Option, diff --git a/giterated-daemon/src/backend/git/tags.rs b/giterated-daemon/src/backend/git/tags.rs index 723a512..11e2d42 100644 --- a/giterated-daemon/src/backend/git/tags.rs +++ b/giterated-daemon/src/backend/git/tags.rs @@ -1,13 +1,57 @@ use anyhow::Error; use giterated_models::{ object::Object, - repository::{Commit, CommitSignature, Repository, RepositoryTag, RepositoryTagsRequest}, + repository::{ + Commit, CommitSignature, Repository, RepositoryTag, RepositoryTagRequest, + RepositoryTagsRequest, + }, }; use giterated_stack::{AuthenticatedUser, GiteratedStack, OperationState, StackOperationState}; -use super::GitBackend; +use super::{parse_trim_git_message, GitBackend, GitBackendError}; impl GitBackend { + /// Converts a git2 tag into our own type, couldn't implement [`From`] or impl on [`RepositoryTag`] from here. + pub fn git2_annotated_tag_to_own_tag( + id: String, + name: String, + tag: git2::Tag, + ) -> RepositoryTag { + // Get the tag message and split it into a summary and body + let (summary, body) = if let Some(message) = tag.message() { + parse_trim_git_message(message) + } else { + (None, None) + }; + + // Get the commit the tag is (possibly) pointing to + let commit = tag + .peel() + .map(|obj| obj.into_commit().ok()) + .ok() + .flatten() + .map(|c| Commit::from(c)); + // Get the author of the tag + let author: Option = tag.tagger().map(|s| s.into()); + // Get the time the tag or pointed commit was created + let time = if let Some(ref author) = author { + Some(author.time) + } else { + // Get possible commit time if the tag has no author time + commit.as_ref().map(|c| c.time.clone()) + }; + + RepositoryTag { + id: id.to_string(), + name: name.to_string(), + summary, + body, + author, + time, + commit, + } + } + /// .0: List of tags in passed range /// .1: Total amount of tags pub async fn handle_repository_get_tags( @@ -31,65 +75,11 @@ impl GitBackend { // Find the tag so we can get the messages attached if any if let Ok(tag) = git.find_tag(id) { - // Get the tag message and split it into a summary and body - let (summary, body) = if let Some(message) = tag.message() { - // Iterate over the lines - let mut lines = message - .lines() - .map(|line| { - // Trim the whitespace for every line - let mut whitespace_removed = String::with_capacity(line.len()); - - line.split_whitespace().for_each(|word| { - if !whitespace_removed.is_empty() { - whitespace_removed.push(' '); - } - - whitespace_removed.push_str(word); - }); - - whitespace_removed - }) - .collect::>(); - - let summary = Some(lines.remove(0)); - let body = if lines.is_empty() { - None - } else { - Some(lines.join("\n")) - }; - - (summary, body) - } else { - (None, None) - }; - - // Get the commit the tag is (possibly) pointing to - let commit = tag - .peel() - .map(|obj| obj.into_commit().ok()) - .ok() - .flatten() - .map(|c| Commit::from(c)); - // Get the author of the tag - let author: Option = tag.tagger().map(|s| s.into()); - // Get the time the tag or pointed commit was created - let time = if let Some(ref author) = author { - Some(author.time) - } else { - // Get possible commit time if the tag has no author time - commit.as_ref().map(|c| c.time.clone()) - }; - - tags.push(RepositoryTag { - id: id.to_string(), - name: name.to_string(), - summary, - body, - author, - time, - commit, - }); + tags.push(Self::git2_annotated_tag_to_own_tag( + id.to_string(), + name, + tag, + )); } else { // Lightweight commit, we try and find the commit it's pointing to let commit = git.find_commit(id).ok().map(|c| Commit::from(c)); @@ -132,4 +122,46 @@ impl GitBackend { Ok((tags, tag_count)) } + + pub async fn handle_repository_get_tag( + &mut self, + requester: &Option, + repository_object: &mut Object<'_, StackOperationState, Repository, GiteratedStack>, + OperationState(_operation_state): OperationState, + request: &RepositoryTagRequest, + ) -> Result { + let repository = repository_object.object(); + let git = self + .open_repository_and_check_permissions(&repository.owner, &repository.name, requester) + .await?; + + // Get the tag id by parsing the ref + let full_ref_name = format!("refs/tags/{}", request.name.clone()); + let tag_id = git + .refname_to_id(&full_ref_name) + .map_err(|_| GitBackendError::RefNotFound(full_ref_name.clone()))?; + + let tag = git.find_tag(tag_id); + if let Ok(tag) = tag { + // Convert the annotated tag into our own type + Ok(Self::git2_annotated_tag_to_own_tag( + tag_id.to_string(), + request.name.clone(), + tag, + )) + } else { + // Lightweight tag, we try and find the commit it's pointing to + let commit = git.find_commit(tag_id).ok().map(|c| Commit::from(c)); + + Ok(RepositoryTag { + id: tag_id.to_string(), + name: request.name.clone(), + summary: None, + body: None, + author: None, + time: commit.as_ref().map(|c| c.time.clone()), + commit, + }) + } + } } diff --git a/giterated-daemon/src/backend/mod.rs b/giterated-daemon/src/backend/mod.rs index 28b3947..8770bde 100644 --- a/giterated-daemon/src/backend/mod.rs +++ b/giterated-daemon/src/backend/mod.rs @@ -24,12 +24,13 @@ use giterated_models::repository::{ RepositoryFileFromIdRequest, RepositoryFileFromPathRequest, RepositoryFileInspectRequest, RepositoryIssue, RepositoryIssueLabelsRequest, RepositoryIssuesCountRequest, RepositoryIssuesRequest, RepositoryLastCommitOfFileRequest, RepositoryStatistics, - RepositoryStatisticsRequest, RepositorySummary, RepositoryTag, RepositoryTagsRequest, - RepositoryTreeEntry, + RepositoryStatisticsRequest, RepositorySummary, RepositoryTag, RepositoryTagRequest, + RepositoryTagsRequest, RepositoryTreeEntry, }; use giterated_models::user::User; +// TODO: Document all functions #[async_trait(?Send)] pub trait RepositoryBackend { async fn create_repository( @@ -121,6 +122,13 @@ pub trait RepositoryBackend { OperationState(operation_state): OperationState, request: &RepositoryTagsRequest, ) -> Result<(Vec, usize), Error>; + async fn repository_get_tag( + &mut self, + requester: &Option, + repository_object: &mut Object<'_, StackOperationState, Repository, GiteratedStack>, + OperationState(operation_state): OperationState, + request: &RepositoryTagRequest, + ) -> Result; async fn exists( &mut self, requester: &Option, diff --git a/giterated-daemon/src/database_backend/handler.rs b/giterated-daemon/src/database_backend/handler.rs index dbf9cca..6a27e4c 100644 --- a/giterated-daemon/src/database_backend/handler.rs +++ b/giterated-daemon/src/database_backend/handler.rs @@ -12,7 +12,8 @@ use giterated_models::{ RepositoryDiffRequest, RepositoryFile, RepositoryFileFromIdRequest, RepositoryFileFromPathRequest, RepositoryFileInspectRequest, RepositoryInfoRequest, RepositoryLastCommitOfFileRequest, RepositoryStatistics, RepositoryStatisticsRequest, - RepositorySummary, RepositoryTag, RepositoryTagsRequest, RepositoryView, Visibility, + RepositorySummary, RepositoryTag, RepositoryTagRequest, RepositoryTagsRequest, + RepositoryView, Visibility, }, user::{User, UserRepositoriesRequest}, }; @@ -229,6 +230,34 @@ pub async fn repository_get_tags( Ok(branches) } +pub async fn repository_get_tag( + object: Repository, + operation: RepositoryTagRequest, + state: DatabaseBackend, + OperationState(operation_state): OperationState, + backend: GiteratedStack, + requester: Option, +) -> Result> { + let mut object = backend + .get_object::(&object.to_string(), &operation_state) + .await + .unwrap(); + + let mut repository_backend = state.repository_backend.lock().await; + let branches = repository_backend + .repository_get_tag( + &requester, + &mut object, + OperationState(operation_state), + &operation, + ) + .await + .as_internal_error()?; + drop(repository_backend); + + Ok(branches) +} + pub async fn repository_file_from_id( object: Repository, operation: RepositoryFileFromIdRequest, diff --git a/giterated-daemon/src/database_backend/mod.rs b/giterated-daemon/src/database_backend/mod.rs index 3e19a7f..0167d54 100644 --- a/giterated-daemon/src/database_backend/mod.rs +++ b/giterated-daemon/src/database_backend/mod.rs @@ -26,8 +26,8 @@ use self::handler::{ instance_authentication_request, instance_create_repository_request, instance_registration_request, repository_commit_before, repository_commit_by_id, repository_diff, repository_diff_patch, repository_file_from_id, repository_file_from_path, - repository_get_branch, repository_get_branches, repository_get_statistics, repository_get_tags, - repository_info, repository_last_commit_of_file, user_get_repositories, + repository_get_branch, repository_get_branches, repository_get_statistics, repository_get_tag, + repository_get_tags, repository_info, repository_last_commit_of_file, user_get_repositories, }; /// A backend implementation which attempts to resolve data from the instance's database. @@ -97,7 +97,8 @@ impl DatabaseBackend { .operation(repository_commit_before) .operation(repository_get_branches) .operation(repository_get_branch) - .operation(repository_get_tags); + .operation(repository_get_tags) + .operation(repository_get_tag); builder } diff --git a/giterated-models/src/repository/operations.rs b/giterated-models/src/repository/operations.rs index 2320769..7341874 100644 --- a/giterated-models/src/repository/operations.rs +++ b/giterated-models/src/repository/operations.rs @@ -325,6 +325,25 @@ impl GiteratedOperation for RepositoryTagsRequest { type Failure = RepositoryError; } +/// A request to get a single tag by name. +/// +/// # Authentication +/// - Instance Authentication +/// - Validate request against the `issued_for` public key +/// - Validate User token against the user's instance's public key +/// # Authorization +/// - User Authorization +/// - Potential User permissions checks +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RepositoryTagRequest { + pub name: String, +} + +impl GiteratedOperation for RepositoryTagRequest { + type Success = RepositoryTag; + type Failure = RepositoryError; +} + impl + std::fmt::Debug> Object<'_, S, Repository, B> { pub async fn info( &mut self, @@ -494,6 +513,20 @@ impl + std::fmt::Debug> Object<'_, S .await } + pub async fn tag( + &mut self, + name: &str, + operation_state: &S, + ) -> Result> { + self.request::( + RepositoryTagRequest { + name: name.to_string(), + }, + operation_state, + ) + .await + } + // pub async fn issues_count(&mut self) -> Result> { // self.request::(RepositoryIssuesCountRequest) // .await