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:
@@ -0,0 +1,157 @@
|
||||
use crate::pb::*;
|
||||
|
||||
use super::git_cmd;
|
||||
|
||||
pub(crate) fn maintenance_response(out: std::process::Output) -> RepositoryMaintenanceResponse {
|
||||
RepositoryMaintenanceResponse {
|
||||
ok: out.status.success(),
|
||||
stdout: String::from_utf8_lossy(&out.stdout).into_owned(),
|
||||
stderr: String::from_utf8_lossy(&out.stderr).into_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
fn dir_size(path: &std::path::Path) -> u64 {
|
||||
let mut total = 0u64;
|
||||
if let Ok(entries) = std::fs::read_dir(path) {
|
||||
for entry in entries.flatten() {
|
||||
let p = entry.path();
|
||||
if p.is_file() {
|
||||
total += entry.metadata().map(|m| m.len()).unwrap_or(0);
|
||||
} else if p.is_dir() {
|
||||
total += dir_size(&p);
|
||||
}
|
||||
}
|
||||
}
|
||||
total
|
||||
}
|
||||
|
||||
fn count_refs(gb: &crate::bare::GitBare) -> u64 {
|
||||
let out = git_cmd(gb, &["for-each-ref", "--format=%(refname)"]).unwrap_or_else(|_| {
|
||||
std::process::Output {
|
||||
status: Default::default(),
|
||||
stdout: Vec::new(),
|
||||
stderr: Vec::new(),
|
||||
}
|
||||
});
|
||||
String::from_utf8_lossy(&out.stdout)
|
||||
.lines()
|
||||
.filter(|l| !l.is_empty())
|
||||
.count() as u64
|
||||
}
|
||||
|
||||
fn file_len(path: &std::path::Path) -> u64 {
|
||||
std::fs::metadata(path).map(|m| m.len()).unwrap_or(0)
|
||||
}
|
||||
|
||||
pub(crate) fn get_statistics(gb: &crate::bare::GitBare) -> RepositoryStatistics {
|
||||
let size_bytes = dir_size(&gb.bare_dir);
|
||||
|
||||
let mut loose_object_count: u64 = 0;
|
||||
let mut packed_object_count: u64 = 0;
|
||||
let mut packfile_count: u64 = 0;
|
||||
if let Ok(out) = git_cmd(gb, &["count-objects", "-v"]) {
|
||||
for line in String::from_utf8_lossy(&out.stdout).lines() {
|
||||
let line = line.trim();
|
||||
if let Some(v) = line.strip_prefix("count: ") {
|
||||
loose_object_count = v.trim().parse().unwrap_or(0);
|
||||
} else if let Some(v) = line.strip_prefix("in-pack: ") {
|
||||
packed_object_count = v.trim().parse().unwrap_or(0);
|
||||
} else if let Some(v) = line.strip_prefix("packs: ") {
|
||||
packfile_count = v.trim().parse().unwrap_or(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let reference_count = count_refs(gb);
|
||||
let commit_graph_size_bytes = file_len(&gb.bare_dir.join("objects/info/commit-graph"));
|
||||
let multi_pack_index_size_bytes = file_len(&gb.bare_dir.join("objects/pack/multi-pack-index"));
|
||||
|
||||
RepositoryStatistics {
|
||||
size_bytes,
|
||||
loose_object_count,
|
||||
packed_object_count,
|
||||
packfile_count,
|
||||
reference_count,
|
||||
commit_graph_size_bytes,
|
||||
multi_pack_index_size_bytes,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn check_health(
|
||||
gb: &crate::bare::GitBare,
|
||||
connectivity_only: bool,
|
||||
) -> Result<RepositoryHealthResponse, tonic::Status> {
|
||||
let mut args: Vec<&str> = vec!["fsck"];
|
||||
if connectivity_only {
|
||||
args.push("--connectivity-only");
|
||||
}
|
||||
let out = git_cmd(gb, &args)?;
|
||||
let text = String::from_utf8_lossy(&out.stdout);
|
||||
let mut warnings = Vec::new();
|
||||
let mut errors = Vec::new();
|
||||
for line in text.lines() {
|
||||
if line.starts_with("error:") {
|
||||
errors.push(line.to_string());
|
||||
} else if line.starts_with("warning:") {
|
||||
warnings.push(line.to_string());
|
||||
}
|
||||
}
|
||||
Ok(RepositoryHealthResponse {
|
||||
ok: out.status.success(),
|
||||
warnings,
|
||||
errors,
|
||||
statistics: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn run_gc(
|
||||
gb: &crate::bare::GitBare,
|
||||
prune: bool,
|
||||
aggressive: bool,
|
||||
) -> Result<RepositoryMaintenanceResponse, tonic::Status> {
|
||||
let mut args: Vec<&str> = vec!["gc"];
|
||||
if prune {
|
||||
args.push("--prune=now");
|
||||
}
|
||||
if aggressive {
|
||||
args.push("--aggressive");
|
||||
}
|
||||
let out = git_cmd(gb, &args)?;
|
||||
Ok(maintenance_response(out))
|
||||
}
|
||||
|
||||
pub(crate) fn run_repack(
|
||||
gb: &crate::bare::GitBare,
|
||||
full: bool,
|
||||
write_bitmaps: bool,
|
||||
write_multi_pack_index: bool,
|
||||
) -> Result<RepositoryMaintenanceResponse, tonic::Status> {
|
||||
let mut args: Vec<&str> = vec!["repack", "-d"];
|
||||
if full {
|
||||
args.push("-a");
|
||||
}
|
||||
if write_bitmaps {
|
||||
args.push("--write-bitmap-index");
|
||||
}
|
||||
if write_multi_pack_index {
|
||||
args.push("--write-midx");
|
||||
}
|
||||
let out = git_cmd(gb, &args)?;
|
||||
Ok(maintenance_response(out))
|
||||
}
|
||||
|
||||
pub(crate) fn run_commit_graph_write(
|
||||
gb: &crate::bare::GitBare,
|
||||
split: bool,
|
||||
replace: bool,
|
||||
) -> Result<RepositoryMaintenanceResponse, tonic::Status> {
|
||||
let mut args: Vec<&str> = vec!["commit-graph", "write"];
|
||||
if split {
|
||||
args.push("--split");
|
||||
}
|
||||
if !replace {
|
||||
args.push("--append");
|
||||
}
|
||||
let out = git_cmd(gb, &args)?;
|
||||
Ok(maintenance_response(out))
|
||||
}
|
||||
Reference in New Issue
Block a user