Files
gitks/server/repository_maint.rs
T
zhenyi d243dce027 refactor(server): replace custom remote clients with macro-based implementation
- Replaced manual remote client functions with remote_client! macro for archive, blame, branch, commit, and diff services
- Simplified remote client creation logic using declarative macro approach
- Maintained same functionality while reducing code duplication across services

security(bare): enhance path traversal protection with comprehensive validation

- Added early relative_path validation to prevent path traversal attacks
- Implemented unified path validation to avoid TOCTOU race conditions
- Enhanced canonicalization checks for both existing and non-existent paths
- Added detailed logging for path traversal detection attempts

feat(cache): migrate from CLruCache to Moka with TTL and invalidation support

- Replaced clru dependency with moka for improved caching capabilities
- Added 300-second time-to-live for cache entries
- Implemented repository-specific cache invalidation mechanism
- Enhanced cache operations with thread-safe async support

refactor(commit): improve security validation for commit operations

- Added ref name validation to prevent command injection in cherry_pick_commit
- Implemented revision validation for commit selectors
- Added comprehensive input validation for create_commit parameters
- Enhanced file path validation to prevent traversal
2026-06-08 09:43:57 +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: out.status.success(),
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: out.status.success(),
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))
}