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
+235
View File
@@ -0,0 +1,235 @@
mod common;
use gitks::bare::GitBare;
use gitks::pb::repository_service_server::RepositoryService;
use gitks::pb::*;
use gitks::server::GitksService;
fn header(gb: &GitBare) -> RepositoryHeader {
let parent = gb.bare_dir.parent().unwrap().to_string_lossy().into_owned();
let name = gb
.bare_dir
.file_name()
.unwrap()
.to_string_lossy()
.into_owned();
RepositoryHeader {
storage_path: parent,
relative_path: name,
..Default::default()
}
}
fn req<T>(gb: &GitBare, f: impl FnOnce(RepositoryHeader) -> T) -> tonic::Request<T> {
tonic::Request::new(f(header(gb)))
}
#[tokio::test]
async fn test_get_repository() {
let (_dir, gb) = common::setup_bare_repo();
let repo = GitksService
.get_repository(req(&gb, |r| GetRepositoryRequest {
repository: Some(r),
}))
.await
.unwrap()
.into_inner();
assert!(repo.bare);
assert_eq!(repo.object_format, ObjectFormat::Sha1 as i32);
assert_eq!(repo.default_branch, "main");
}
#[tokio::test]
async fn test_init_and_delete_repository() {
let dir = tempfile::tempdir().unwrap();
let storage = dir.path().to_string_lossy().into_owned();
let hdr = RepositoryHeader {
storage_path: storage.clone(),
relative_path: "new-repo".into(),
..Default::default()
};
let svc = GitksService;
svc.init_repository(tonic::Request::new(InitRepositoryRequest {
repository: Some(hdr.clone()),
bare: true,
..Default::default()
}))
.await
.unwrap();
assert!(
svc.repository_exists(tonic::Request::new(RepositoryExistsRequest {
repository: Some(hdr.clone())
}))
.await
.unwrap()
.into_inner()
.exists
);
svc.delete_repository(tonic::Request::new(DeleteRepositoryRequest {
repository: Some(hdr.clone()),
}))
.await
.unwrap();
assert!(
!svc.repository_exists(tonic::Request::new(RepositoryExistsRequest {
repository: Some(hdr)
}))
.await
.unwrap()
.into_inner()
.exists
);
}
#[tokio::test]
async fn test_get_object_format() {
let (_dir, gb) = common::setup_bare_repo();
let resp = GitksService
.get_object_format(req(&gb, |r| RepositoryObjectFormatRequest {
repository: Some(r),
}))
.await
.unwrap()
.into_inner();
assert_eq!(resp.object_format, ObjectFormat::Sha1 as i32);
}
#[tokio::test]
async fn test_get_set_default_branch() {
let (_dir, gb) = common::setup_bare_repo();
let h = header(&gb);
assert_eq!(
GitksService
.get_default_branch(tonic::Request::new(GetDefaultBranchRequest {
repository: Some(h.clone())
}))
.await
.unwrap()
.into_inner()
.name,
"main"
);
GitksService
.set_default_branch(tonic::Request::new(SetDefaultBranchRequest {
repository: Some(h.clone()),
name: "feature".into(),
}))
.await
.unwrap();
assert_eq!(
GitksService
.get_default_branch(tonic::Request::new(GetDefaultBranchRequest {
repository: Some(h)
}))
.await
.unwrap()
.into_inner()
.name,
"feature"
);
}
#[tokio::test]
async fn test_get_set_repository_config() {
let (_dir, gb) = common::setup_bare_repo();
GitksService
.set_repository_config(tonic::Request::new(SetRepositoryConfigRequest {
repository: Some(header(&gb)),
entries: vec![RepositoryConfigEntry {
key: "test.key".into(),
values: vec!["val1".into(), "val2".into()],
}],
}))
.await
.unwrap();
let entry = GitksService
.get_repository_config(tonic::Request::new(GetRepositoryConfigRequest {
repository: Some(header(&gb)),
keys: vec!["test.key".into()],
}))
.await
.unwrap()
.into_inner()
.entries
.into_iter()
.find(|e| e.key == "test.key")
.unwrap();
assert_eq!(entry.values, vec!["val1", "val2"]);
}
#[tokio::test]
async fn test_get_repository_statistics() {
let (_dir, gb) = common::setup_bare_repo();
let s = GitksService
.get_repository_statistics(req(&gb, |r| RepositoryStatisticsRequest {
repository: Some(r),
}))
.await
.unwrap()
.into_inner();
assert!(s.size_bytes > 0);
assert!(s.loose_object_count > 0 || s.packed_object_count > 0);
assert!(s.reference_count >= 2);
}
#[tokio::test]
async fn test_check_repository_health() {
let (_dir, gb) = common::setup_bare_repo();
let h = GitksService
.check_repository_health(tonic::Request::new(RepositoryHealthRequest {
repository: Some(header(&gb)),
connectivity_only: true,
}))
.await
.unwrap()
.into_inner();
assert!(h.ok && h.errors.is_empty());
}
#[tokio::test]
async fn test_garbage_collect() {
let (_dir, gb) = common::setup_bare_repo();
assert!(
GitksService
.garbage_collect(tonic::Request::new(GarbageCollectRequest {
repository: Some(header(&gb)),
..Default::default()
}))
.await
.unwrap()
.into_inner()
.ok
);
}
#[tokio::test]
async fn test_repack() {
let (_dir, gb) = common::setup_bare_repo();
assert!(
GitksService
.repack(tonic::Request::new(RepackRequest {
repository: Some(header(&gb)),
..Default::default()
}))
.await
.unwrap()
.into_inner()
.ok
);
}
#[tokio::test]
async fn test_write_commit_graph() {
let (_dir, gb) = common::setup_bare_repo();
assert!(
GitksService
.write_commit_graph(tonic::Request::new(WriteCommitGraphRequest {
repository: Some(header(&gb)),
..Default::default()
}))
.await
.unwrap()
.into_inner()
.ok
);
}