feat(server): add comprehensive Git repository services with test coverage

- Implement ArchiveService for repository archive operations
- Add BlameService for Git blame functionality
- Create BranchService with full branch management capabilities
- Integrate CommitService for commit operations and history
- Add DiffService for generating diffs and patches
- Implement MergeService with conflict resolution features
- Add PackService for Git packfile operations
- Create TagService for Git tag management
- Add TreeService for Git tree operations
- Implement comprehensive repository management functions
- Add repository statistics and health checking capabilities
- Include garbage collection and repacking operations
- Add repository configuration management
- Implement error handling and status conversion utilities
- Add test suite covering all repository operations
- Create utility functions for Git command execution
- Add streaming response support for large data operations
- Implement request resolution and validation helpers
This commit is contained in:
zhenyi
2026-06-04 14:10:21 +08:00
parent 737e934043
commit 998f393ed0
13 changed files with 1312 additions and 0 deletions
+115
View File
@@ -0,0 +1,115 @@
mod archive;
mod blame;
mod branch;
mod commit;
mod diff;
mod merge;
mod pack;
mod repository;
mod repository_maint;
mod tag;
mod tree;
use std::path::PathBuf;
use tokio_stream::wrappers::ReceiverStream;
use crate::bare::GitBare;
use crate::error::GitError;
use crate::pb::{
archive_service_server, blame_service_server, branch_service_server, commit_service_server,
diff_service_server, merge_service_server, pack_service_server, repository_service_server,
tag_service_server, tree_service_server,
};
#[derive(Clone, Copy)]
pub struct GitksService;
pub(crate) fn into_status(e: GitError) -> tonic::Status {
match &e {
GitError::NotFound(_)
| GitError::ObjectNotFound(_)
| GitError::RefNotFound(_)
| GitError::RepoNotFound => tonic::Status::not_found(e.to_string()),
GitError::InvalidArgument(_) => tonic::Status::invalid_argument(e.to_string()),
GitError::PermissionDenied(_) => tonic::Status::permission_denied(e.to_string()),
GitError::Locked(_) => tonic::Status::failed_precondition(e.to_string()),
GitError::AuthFailed(_) => tonic::Status::unauthenticated(e.to_string()),
GitError::NotBareRepository => tonic::Status::failed_precondition(e.to_string()),
_ => tonic::Status::internal(e.to_string()),
}
}
impl From<GitError> for tonic::Status {
fn from(e: GitError) -> Self {
into_status(e)
}
}
pub(crate) fn resolve(
header: Option<&crate::pb::RepositoryHeader>,
) -> Result<GitBare, tonic::Status> {
let header = header.ok_or_else(|| tonic::Status::invalid_argument("repository is required"))?;
GitBare::from_repository_header(header).map_err(into_status)
}
pub(crate) fn resolve_for_init(
header: Option<&crate::pb::RepositoryHeader>,
) -> Result<PathBuf, tonic::Status> {
let header = header.ok_or_else(|| tonic::Status::invalid_argument("repository is required"))?;
let storage_path = header.storage_path.trim();
let relative_path = header.relative_path.trim();
if storage_path.is_empty() {
return Err(tonic::Status::invalid_argument("storage_path is required"));
}
let p = std::path::Path::new(storage_path);
if !p.is_absolute() {
return Err(tonic::Status::invalid_argument(
"storage_path must be an absolute path",
));
}
let base = PathBuf::from(p);
Ok(if !relative_path.is_empty() {
base.join(relative_path)
} else {
base
})
}
pub(crate) fn into_stream<T: Send + 'static>(
items: Vec<T>,
) -> ReceiverStream<Result<T, tonic::Status>> {
let (tx, rx) = tokio::sync::mpsc::channel(items.len().max(1));
for item in items {
let _ = tx.try_send(Ok(item));
}
ReceiverStream::new(rx)
}
pub(crate) fn git_cmd(gb: &GitBare, args: &[&str]) -> Result<std::process::Output, tonic::Status> {
let mut full_args: Vec<String> = vec![
"--git-dir".into(),
gb.bare_dir.to_string_lossy().into_owned(),
];
full_args.extend(args.iter().map(|s| s.to_string()));
std::process::Command::new("git")
.args(&full_args)
.output()
.map_err(|e| tonic::Status::internal(e.to_string()))
}
pub async fn serve(addr: std::net::SocketAddr) -> Result<(), tonic::transport::Error> {
let svc = GitksService;
tonic::transport::Server::builder()
.add_service(repository_service_server::RepositoryServiceServer::new(svc))
.add_service(archive_service_server::ArchiveServiceServer::new(svc))
.add_service(blame_service_server::BlameServiceServer::new(svc))
.add_service(branch_service_server::BranchServiceServer::new(svc))
.add_service(commit_service_server::CommitServiceServer::new(svc))
.add_service(diff_service_server::DiffServiceServer::new(svc))
.add_service(merge_service_server::MergeServiceServer::new(svc))
.add_service(pack_service_server::PackServiceServer::new(svc))
.add_service(tag_service_server::TagServiceServer::new(svc))
.add_service(tree_service_server::TreeServiceServer::new(svc))
.serve(addr)
.await
}