Files
gitks/server/repository_maint.rs
T
zhenyi f5044fb099 refactor(docker): optimize Docker build process and update configurations
- Replace direct Rust build with cargo-chef multi-stage build pattern
- Switch base image from debian:bookworm-slim to ubuntu:26.04
- Add .codegraph to .dockerignore and data to .gitignore
- Introduce Dockerfile.fast for faster builds without optimization
- Add comprehensive .env configuration file with cluster settings
- Create docker-compose.yaml for multi-node cluster setup
- Add cluster routing test case for distributed operations
- Remove unnecessary success status checks in repository maintenance
- Fix error handling in git command executions by properly propagating errors
- Add repository move protection to prevent self-destruct operations
- Simplify conditional logic in actor message validation
- Update remote pack client calls with proper error handling parameters
2026-06-08 18:52:22 +08:00

192 lines
5.7 KiB
Rust

use crate::pb::*;
use super::git_cmd;
pub(crate) fn maintenance_response(out: std::process::Output) -> RepositoryMaintenanceResponse {
RepositoryMaintenanceResponse {
ok: true,
stdout: String::from_utf8_lossy(&out.stdout).into_owned(),
stderr: String::from_utf8_lossy(&out.stderr).into_owned(),
}
}
/// Get approximate repository size using git count-objects instead of
/// recursively scanning the filesystem (which is O(n) and very slow for large repos).
fn dir_size(gb: &crate::bare::GitBare) -> u64 {
let out = git_cmd(gb, &["count-objects", "-v"]).ok();
let text = out
.as_ref()
.map(|o| String::from_utf8_lossy(&o.stdout).into_owned())
.unwrap_or_default();
let mut loose_size_kb = 0u64;
let mut pack_size_kb = 0u64;
let mut garbage_size_kb = 0u64;
for line in text.lines() {
let line = line.trim();
if let Some(val) = line.strip_prefix("size: ") {
loose_size_kb = val.trim().parse().unwrap_or(0);
} else if let Some(val) = line.strip_prefix("size-pack: ") {
pack_size_kb = val.trim().parse().unwrap_or(0);
} else if let Some(val) = line.strip_prefix("size-garbage: ") {
garbage_size_kb = val.trim().parse().unwrap_or(0);
}
}
// count-objects reports sizes in KiB; convert to bytes
(loose_size_kb + pack_size_kb + garbage_size_kb) * 1024
}
fn count_refs(gb: &crate::bare::GitBare) -> u64 {
let out = git_cmd(gb, &["for-each-ref", "--format=%(refname)"]).ok();
out.map(|o| {
String::from_utf8_lossy(&o.stdout)
.lines()
.filter(|l| !l.is_empty())
.count() as u64
})
.unwrap_or(0)
}
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);
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> {
tracing::info!(
repo = %gb.bare_dir.display(),
connectivity_only = connectivity_only,
"running health check"
);
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: true,
warnings,
errors,
statistics: None,
})
}
pub(crate) fn run_gc(
gb: &crate::bare::GitBare,
prune: bool,
aggressive: bool,
) -> Result<RepositoryMaintenanceResponse, tonic::Status> {
tracing::info!(
repo = %gb.bare_dir.display(),
prune = prune,
aggressive = aggressive,
"running garbage collection"
);
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> {
tracing::info!(
repo = %gb.bare_dir.display(),
full = full,
write_bitmaps = write_bitmaps,
write_multi_pack_index = write_multi_pack_index,
"running repack"
);
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> {
tracing::info!(
repo = %gb.bare_dir.display(),
split = split,
replace = replace,
"writing commit-graph"
);
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))
}