diff --git a/giterated-daemon/src/backend/git.rs b/giterated-daemon/src/backend/git.rs index 306dee9..337602f 100644 --- a/giterated-daemon/src/backend/git.rs +++ b/giterated-daemon/src/backend/git.rs @@ -8,7 +8,7 @@ use giterated_models::repository::{ Commit, DefaultBranch, Description, IssueLabel, LatestCommit, Repository, RepositoryFileInspectRequest, RepositoryIssue, RepositoryIssueLabelsRequest, RepositoryIssuesCountRequest, RepositoryIssuesRequest, RepositoryObjectType, - RepositoryTreeEntry, RepositoryVisibility, Visibility, RepositoryFile, RepositoryFileFromIdRequest, + RepositoryTreeEntry, RepositoryVisibility, Visibility, RepositoryFile, RepositoryFileFromIdRequest, RepositoryDiffRequest, RepositoryDiff, }; use giterated_models::settings::{AnySetting, Setting}; use giterated_models::user::{User, UserParseError}; @@ -95,6 +95,10 @@ pub enum GitBackendError { InvalidObjectId(String), #[error("Blob with ID `{0}` not found")] BlobNotFound(String), + #[error("Tree with ID `{0}` not found")] + TreeNotFound(String), + #[error("Failed diffing tree with ID `{0}` to tree with ID `{1}`")] + FailedDiffing(String, String), } pub struct GitBackend { @@ -694,6 +698,95 @@ impl RepositoryBackend for GitBackend { Ok(file) } + + async fn repository_diff( + &mut self, + requester: Option<&User>, + repository: &Repository, + request: &RepositoryDiffRequest, + ) -> Result { + // TODO: Get rid of all this duplicate code + let repository = match self + .find_by_owner_user_name( + // &request.owner.instance.url, + &repository.owner, + &repository.name, + ) + .await + { + Ok(repository) => repository, + Err(err) => return Err(Box::new(err).into()), + }; + + if let Some(requester) = requester { + if !repository.can_user_view_repository(Some(requester)) { + return Err(Box::new(GitBackendError::RepositoryNotFound { + owner_user: repository.owner_user.to_string(), + name: repository.name.clone(), + }) + .into()); + } + } else if matches!(repository.visibility, RepositoryVisibility::Private) { + info!("Unauthenticated"); + // Unauthenticated users can never view private repositories + + return Err(Box::new(GitBackendError::RepositoryNotFound { + owner_user: repository.owner_user.to_string(), + name: repository.name.clone(), + }) + .into()); + } + + let git = match repository.open_git2_repository(&self.repository_folder) { + Ok(git) => git, + Err(err) => return Err(Box::new(err).into()), + }; + + // Parse the passed object ids + let oid_old = match git2::Oid::from_str(request.old_id.as_str()) { + Ok(oid) => oid, + Err(_) => return Err(Box::new(GitBackendError::InvalidObjectId(request.old_id.clone())).into()), + }; + let oid_new = match git2::Oid::from_str(request.new_id.as_str()) { + Ok(oid) => oid, + Err(_) => return Err(Box::new(GitBackendError::InvalidObjectId(request.new_id.clone())).into()), + }; + + // Get the trees of those object ids + let tree_old = match git.find_tree(oid_old) { + Ok(tree) => tree, + Err(_) => return Err(Box::new(GitBackendError::TreeNotFound(oid_old.to_string())).into()), + }; + let tree_new = match git.find_tree(oid_new) { + Ok(tree) => tree, + Err(_) => return Err(Box::new(GitBackendError::TreeNotFound(oid_new.to_string())).into()), + }; + + // Diff the two trees against each other + let diff = match git.diff_tree_to_tree(Some(&tree_old), Some(&tree_new), None) { + Ok(diff) => diff, + Err(_) => return Err(Box::new(GitBackendError::FailedDiffing(oid_old.to_string(), oid_new.to_string())).into()), + }; + + // Should be safe to unwrap? + let stats = diff.stats().unwrap(); + + // Honestly not quite sure what is going on here, could not find documentation. + // Print the entire patch + let mut patch = String::new(); + + diff.print(git2::DiffFormat::Patch, |_, _, line| { + patch.push_str(std::str::from_utf8(line.content()).unwrap()); + true + }).unwrap(); + + Ok(RepositoryDiff { + files_changed: stats.files_changed(), + insertions: stats.insertions(), + deletions: stats.deletions(), + patch, + }) + } } impl IssuesBackend for GitBackend { diff --git a/giterated-daemon/src/backend/mod.rs b/giterated-daemon/src/backend/mod.rs index 21269a5..a09ee36 100644 --- a/giterated-daemon/src/backend/mod.rs +++ b/giterated-daemon/src/backend/mod.rs @@ -18,7 +18,7 @@ use giterated_models::instance::{ use giterated_models::repository::{ IssueLabel, Repository, RepositoryFileInspectRequest, RepositoryIssue, RepositoryIssueLabelsRequest, RepositoryIssuesCountRequest, RepositoryIssuesRequest, - RepositorySummary, RepositoryTreeEntry, RepositoryFileFromIdRequest, RepositoryFile, + RepositorySummary, RepositoryTreeEntry, RepositoryFileFromIdRequest, RepositoryFile, RepositoryDiffRequest, RepositoryDiff, }; use giterated_models::settings::AnySetting; use giterated_models::user::User; @@ -43,6 +43,12 @@ pub trait RepositoryBackend { repository: &Repository, request: &RepositoryFileFromIdRequest, ) -> Result; + async fn repository_diff( + &mut self, + requester: Option<&User>, + repository: &Repository, + request: &RepositoryDiffRequest, + ) -> Result; async fn get_value( &mut self, user: &Repository, diff --git a/giterated-daemon/src/database_backend/handler.rs b/giterated-daemon/src/database_backend/handler.rs index f74f6ad..2b4196d 100644 --- a/giterated-daemon/src/database_backend/handler.rs +++ b/giterated-daemon/src/database_backend/handler.rs @@ -8,7 +8,7 @@ use giterated_models::{ operation::{AnyOperation, GiteratedOperation}, repository::{ DefaultBranch, Description, LatestCommit, Repository, RepositoryFileInspectRequest, - RepositoryInfoRequest, RepositorySummary, RepositoryView, Visibility, RepositoryFile, RepositoryFileFromIdRequest, + RepositoryInfoRequest, RepositorySummary, RepositoryView, Visibility, RepositoryFile, RepositoryFileFromIdRequest, RepositoryDiff, RepositoryDiffRequest, }, settings::{AnySetting, GetSetting, GetSettingError, SetSetting, SetSettingError}, user::{User, UserRepositoriesRequest}, @@ -279,6 +279,34 @@ pub fn repository_file_from_id( }.boxed() } +pub fn repository_diff( + object: &Repository, + operation: RepositoryDiffRequest, + state: DatabaseBackend, +) -> BoxFuture<'static, Result>> { + let object = object.clone(); + + async move { + let object = state + .get_object::(&object.to_string()) + .await + .unwrap(); + + let mut repository_backend = state.repository_backend.lock().await; + let file = repository_backend + .repository_diff( + None, + object.object(), + &operation, + ) + .await + .map_err(|err| OperationError::Internal(format!("{:?}", err)))?; + drop(repository_backend); + + Ok(file) + }.boxed() +} + pub fn repository_get_value( object: &Repository, operation: GetValue>, diff --git a/giterated-daemon/src/database_backend/mod.rs b/giterated-daemon/src/database_backend/mod.rs index 558c684..95147e1 100644 --- a/giterated-daemon/src/database_backend/mod.rs +++ b/giterated-daemon/src/database_backend/mod.rs @@ -18,7 +18,7 @@ use crate::backend::{RepositoryBackend, UserBackend}; use self::handler::{ repository_get_setting, repository_get_value, repository_info, repository_set_setting, - user_get_repositories, user_get_setting, user_get_value, user_set_setting, OperationHandlers, repository_file_from_id, + user_get_repositories, user_get_setting, user_get_value, user_set_setting, OperationHandlers, repository_file_from_id, repository_diff, }; #[derive(Clone, Debug)] @@ -118,6 +118,7 @@ impl ObjectBackend for DatabaseBackend { handler .insert(repository_info) .insert(repository_file_from_id) + .insert(repository_diff) .insert(repository_get_value) .insert(repository_get_setting) .insert(repository_set_setting); @@ -256,7 +257,7 @@ mod test { use giterated_models::repository::{ Description, Repository, RepositoryFileInspectRequest, RepositorySummary, - RepositoryTreeEntry, RepositoryFileFromIdRequest, RepositoryFile + RepositoryTreeEntry, RepositoryFileFromIdRequest, RepositoryFile, RepositoryDiff, RepositoryDiffRequest }; use giterated_models::settings::AnySetting; use giterated_models::user::{DisplayName, User}; @@ -350,6 +351,14 @@ mod test { ) -> Result { todo!() } + async fn repository_diff( + &mut self, + requester: Option<&User>, + repository: &Repository, + request: &RepositoryDiffRequest, + ) -> Result { + todo!() + } async fn get_value( &mut self, _repository: &Repository, diff --git a/giterated-models/src/repository/mod.rs b/giterated-models/src/repository/mod.rs index 8f63b15..e1c8af4 100644 --- a/giterated-models/src/repository/mod.rs +++ b/giterated-models/src/repository/mod.rs @@ -147,6 +147,18 @@ pub struct RepositoryFile { pub size: usize, } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RepositoryDiff { + /// Total number of files changed + pub files_changed: usize, + /// Total number of insertions + pub insertions: usize, + /// Total number of deletions + pub deletions: usize, + /// Preferably unified patch, probably a git patch. + pub patch: String, +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub enum RepositoryObjectType { Tree, diff --git a/giterated-models/src/repository/operations.rs b/giterated-models/src/repository/operations.rs index c94e210..b947114 100644 --- a/giterated-models/src/repository/operations.rs +++ b/giterated-models/src/repository/operations.rs @@ -7,7 +7,7 @@ use crate::{ operation::GiteratedOperation, }; -use super::{IssueLabel, Repository, RepositoryIssue, RepositoryTreeEntry, RepositoryView, RepositoryFile}; +use super::{IssueLabel, Repository, RepositoryIssue, RepositoryTreeEntry, RepositoryView, RepositoryFile, RepositoryDiff}; /// A request to get a repository's information. /// @@ -47,6 +47,26 @@ impl GiteratedOperation for RepositoryFileFromIdRequest { type Failure = RepositoryError; } +/// A request to get the difference between two repository trees. +/// +/// # 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 RepositoryDiffRequest { + pub old_id: String, + pub new_id: String, +} + +impl GiteratedOperation for RepositoryDiffRequest { + type Success = RepositoryDiff; + type Failure = RepositoryError; +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct RepositoryIssuesCountRequest; @@ -135,6 +155,14 @@ impl Object<'_, Repository, B> { self.request::(RepositoryFileFromIdRequest(id)).await } + pub async fn diff( + &mut self, + old_id: String, + new_id: String, + ) -> Result> { + self.request::(RepositoryDiffRequest { old_id, new_id }).await + } + // pub async fn issues_count(&mut self) -> Result> { // self.request::(RepositoryIssuesCountRequest) // .await