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)"]).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); 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 { 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 { 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 { 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 { 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)) }