use anyhow::Error; use giterated_models::{ object::Object, repository::{ Commit, Repository, RepositoryChunkLine, RepositoryDiff, RepositoryDiffFile, RepositoryDiffFileChunk, RepositoryDiffFileInfo, RepositoryDiffFileStatus, RepositoryDiffPatchRequest, RepositoryDiffRequest, }, }; use giterated_stack::{AuthenticatedUser, GiteratedStack, OperationState, StackOperationState}; use super::{GitBackend, GitBackendError}; impl GitBackend { pub async fn handle_repository_diff( &mut self, requester: &Option, repository_object: &mut Object<'_, StackOperationState, Repository, GiteratedStack>, OperationState(_operation_state): OperationState, request: &RepositoryDiffRequest, ) -> Result { let repository = repository_object.object(); let git = self .open_repository_and_check_permissions(&repository.owner, &repository.name, requester) .await?; // Parse the passed object ids let oid_old = git2::Oid::from_str(request.old_id.as_str()) .map_err(|_| GitBackendError::InvalidObjectId(request.old_id.clone()))?; let oid_new = git2::Oid::from_str(request.new_id.as_str()) .map_err(|_| GitBackendError::InvalidObjectId(request.new_id.clone()))?; // Get the ids associates commits let commit_old = git .find_commit(oid_old) .map_err(|_| GitBackendError::CommitNotFound(oid_old.to_string()))?; let commit_new = git .find_commit(oid_new) .map_err(|_| GitBackendError::CommitNotFound(oid_new.to_string()))?; // Get the commit trees let tree_old = commit_old .tree() .map_err(|_| GitBackendError::TreeNotFound(oid_old.to_string()))?; let tree_new = commit_new .tree() .map_err(|_| GitBackendError::TreeNotFound(oid_new.to_string()))?; // Diff the two trees against each other let diff = git .diff_tree_to_tree(Some(&tree_old), Some(&tree_new), None) .map_err(|_| { GitBackendError::FailedDiffing(oid_old.to_string(), oid_new.to_string()) })?; // Should be safe to unwrap? let stats = diff.stats().unwrap(); let mut files: Vec = vec![]; diff.deltas().enumerate().for_each(|(i, delta)| { // Parse the old file info from the delta let old_file_info = match delta.old_file().exists() { true => Some(RepositoryDiffFileInfo { id: delta.old_file().id().to_string(), path: delta .old_file() .path() .unwrap() .to_str() .unwrap() .to_string(), size: delta.old_file().size(), binary: delta.old_file().is_binary(), }), false => None, }; // Parse the new file info from the delta let new_file_info = match delta.new_file().exists() { true => Some(RepositoryDiffFileInfo { id: delta.new_file().id().to_string(), path: delta .new_file() .path() .unwrap() .to_str() .unwrap() .to_string(), size: delta.new_file().size(), binary: delta.new_file().is_binary(), }), false => None, }; let mut chunks: Vec = vec![]; if let Some(patch) = git2::Patch::from_diff(&diff, i).ok().flatten() { for chunk_num in 0..patch.num_hunks() { if let Ok((chunk, chunk_num_lines)) = patch.hunk(chunk_num) { let mut lines: Vec = vec![]; for line_num in 0..chunk_num_lines { if let Ok(line) = patch.line_in_hunk(chunk_num, line_num) { if let Ok(line_utf8) = String::from_utf8(line.content().to_vec()) { lines.push(RepositoryChunkLine { change_type: line.origin_value().into(), content: line_utf8, old_line_num: line.old_lineno(), new_line_num: line.new_lineno(), }); } continue; } } chunks.push(RepositoryDiffFileChunk { header: String::from_utf8(chunk.header().to_vec()).ok(), old_start: chunk.old_start(), old_lines: chunk.old_lines(), new_start: chunk.new_start(), new_lines: chunk.new_lines(), lines, }); } } }; let file = RepositoryDiffFile { status: RepositoryDiffFileStatus::from(delta.status()), old_file_info, new_file_info, chunks, }; files.push(file); }); Ok(RepositoryDiff { new_commit: Commit::from(commit_new), files_changed: stats.files_changed(), insertions: stats.insertions(), deletions: stats.deletions(), files, }) } pub async fn handle_repository_diff_patch( &mut self, requester: &Option, repository_object: &mut Object<'_, StackOperationState, Repository, GiteratedStack>, OperationState(_operation_state): OperationState, request: &RepositoryDiffPatchRequest, ) -> Result { let repository = repository_object.object(); let git = self .open_repository_and_check_permissions(&repository.owner, &repository.name, requester) .await?; // Parse the passed object ids let oid_old = git2::Oid::from_str(request.old_id.as_str()) .map_err(|_| GitBackendError::InvalidObjectId(request.old_id.clone()))?; let oid_new = git2::Oid::from_str(request.new_id.as_str()) .map_err(|_| GitBackendError::InvalidObjectId(request.new_id.clone()))?; // Get the ids associates commits let commit_old = git .find_commit(oid_old) .map_err(|_| GitBackendError::CommitNotFound(oid_old.to_string()))?; let commit_new = git .find_commit(oid_new) .map_err(|_| GitBackendError::CommitNotFound(oid_new.to_string()))?; // Get the commit trees let tree_old = commit_old .tree() .map_err(|_| GitBackendError::TreeNotFound(oid_old.to_string()))?; let tree_new = commit_new .tree() .map_err(|_| GitBackendError::TreeNotFound(oid_new.to_string()))?; // Diff the two trees against each other let diff = git .diff_tree_to_tree(Some(&tree_old), Some(&tree_new), None) .map_err(|_| { GitBackendError::FailedDiffing(oid_old.to_string(), oid_new.to_string()) })?; // Print the entire patch let mut patch = String::new(); diff.print(git2::DiffFormat::Patch, |_, _, line| { match line.origin() { '+' | '-' | ' ' => patch.push(line.origin()), _ => {} } patch.push_str(std::str::from_utf8(line.content()).unwrap()); true }) .unwrap(); Ok(patch) } }