feat(core): implement Git repository operations with gRPC services

- Add advertise_refs functionality for Git protocol communication
- Implement archive service with TAR/ZIP format support and streaming
- Create blame service for Git file annotation with line tracking
- Add branch management including create, delete, rename and compare operations
- Implement merge checking with conflict detection and fast-forward handling
- Add cherry-pick functionality for applying commits between branches
- Integrate gix library for Git repository operations and object handling
- Add comprehensive test suite covering all Git operations
- Implement proper error handling and repository validation
- Add pagination support for large result sets
- Create protobuf definitions for all Git operations and data structures
- Add build system for gRPC code generation and dependency management
This commit is contained in:
zhenyi
2026-06-04 13:05:38 +08:00
commit dcb0fb74c5
98 changed files with 20569 additions and 0 deletions
+169
View File
@@ -0,0 +1,169 @@
mod common;
use gitks::pb::*;
#[test]
fn test_get_archive_tar() {
let (_dir, gb) = common::setup_bare_repo();
let chunks = gb
.get_archive(ArchiveRequest {
repository: None,
treeish: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
})),
}),
options: Some(ArchiveOptions {
format: archive_options::Format::ArchiveFormatTar as i32,
..Default::default()
}),
})
.expect("get_archive tar");
assert!(!chunks.is_empty(), "should produce archive data");
let total_size: usize = chunks.iter().map(|c| c.data.len()).sum();
assert!(total_size > 0, "archive should not be empty");
}
#[test]
fn test_get_archive_zip() {
let (_dir, gb) = common::setup_bare_repo();
let chunks = gb
.get_archive(ArchiveRequest {
repository: None,
treeish: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
})),
}),
options: Some(ArchiveOptions {
format: archive_options::Format::ArchiveFormatZip as i32,
..Default::default()
}),
})
.expect("get_archive zip");
assert!(!chunks.is_empty());
let data = &chunks[0].data;
assert!(
data.starts_with(b"PK"),
"zip archive should start with PK magic bytes"
);
}
#[test]
fn test_list_archive_entries() {
let (_dir, gb) = common::setup_bare_repo();
let result = gb
.list_archive_entries(ListArchiveEntriesRequest {
repository: None,
treeish: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
})),
}),
pathspec: vec![],
pagination: None,
})
.expect("list_archive_entries");
assert!(!result.entries.is_empty(), "should list entries");
let paths: Vec<&str> = result.entries.iter().map(|e| e.path.as_str()).collect();
assert!(
paths.iter().any(|p| p.contains("README.md")),
"should include README.md, got: {:?}",
paths
);
}
#[test]
fn test_get_archive_with_prefix() {
let (_dir, gb) = common::setup_bare_repo();
let chunks = gb
.get_archive(ArchiveRequest {
repository: None,
treeish: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
})),
}),
options: Some(ArchiveOptions {
format: archive_options::Format::ArchiveFormatTar as i32,
prefix: "project/".into(),
..Default::default()
}),
})
.expect("get_archive with prefix");
assert!(!chunks.is_empty());
}
#[test]
fn test_fsck_clean_repo() {
let (_dir, gb) = common::setup_bare_repo();
let result = gb
.fsck(FsckRequest {
repository: None,
strict: false,
connectivity_only: false,
})
.expect("fsck");
assert!(result.ok);
assert!(result.errors.is_empty());
}
#[test]
fn test_list_packfiles() {
let (_dir, gb) = common::setup_bare_repo();
duct::cmd(
"git",
[
"--git-dir",
gb.bare_dir.to_string_lossy().as_ref(),
"gc",
"--aggressive",
],
)
.run()
.expect("git gc");
let result = gb
.list_packfiles(ListPackfilesRequest {
repository: None,
pagination: None,
})
.expect("list_packfiles");
assert!(
!result.packfiles.is_empty(),
"bare repo should have packfiles after gc"
);
for pf in &result.packfiles {
assert!(pf.size_bytes > 0, "packfile should have size");
assert!(pf.name.ends_with(".pack"));
}
}
#[test]
fn test_advertise_refs() {
let (_dir, gb) = common::setup_bare_repo();
let result = gb
.advertise_refs(AdvertiseRefsRequest {
repository: None,
protocol: None,
service: String::new(),
})
.expect("advertise_refs");
assert!(!result.references.is_empty(), "should have refs");
let ref_names: Vec<&str> = result.references.iter().map(|r| r.name.as_str()).collect();
assert!(
ref_names.iter().any(|n| n.contains("refs/heads/main")),
"should include main branch ref"
);
assert!(
!result.capabilities.is_empty(),
"should advertise capabilities"
);
}