203 lines
6.4 KiB
Rust
203 lines
6.4 KiB
Rust
//! Copyright (c) 2022-2026 GitDataAi All rights reserved.
|
|
|
|
use crate::bare::GitBare;
|
|
use crate::error::GitResult;
|
|
use crate::pb::*;
|
|
|
|
impl GitBare {
|
|
/// Run heuristic optimization based on repo state.
|
|
pub fn optimize_repository(
|
|
&self,
|
|
request: OptimizeRepositoryRequest,
|
|
) -> GitResult<OptimizeRepositoryResponse> {
|
|
let strategy =
|
|
OptimizeStrategy::try_from(request.strategy).unwrap_or(OptimizeStrategy::Heuristic);
|
|
|
|
let mut stdout_all = String::new();
|
|
let mut stderr_all = String::new();
|
|
|
|
match strategy {
|
|
OptimizeStrategy::Heuristic | OptimizeStrategy::Aggressive => {
|
|
let stats = self.get_repository_statistics()?;
|
|
|
|
if (stats.commit_graph_size_bytes == 0 || strategy == OptimizeStrategy::Aggressive)
|
|
&& let Ok(resp) = write_commit_graph(self, false, false)
|
|
{
|
|
if !resp.ok {
|
|
stderr_all.push_str(&resp.stderr);
|
|
}
|
|
stdout_all.push_str(&resp.stdout);
|
|
}
|
|
|
|
let repack_needed = stats.loose_object_count > 1000 || stats.packfile_count > 10;
|
|
|
|
if repack_needed || strategy == OptimizeStrategy::Aggressive {
|
|
let full = strategy == OptimizeStrategy::Aggressive;
|
|
if let Ok(resp) = run_repack(self, full, true, true) {
|
|
if !resp.ok {
|
|
stderr_all.push_str(&resp.stderr);
|
|
}
|
|
stdout_all.push_str(&resp.stdout);
|
|
}
|
|
}
|
|
|
|
if strategy == OptimizeStrategy::Aggressive
|
|
&& let Ok(resp) = run_gc(self, true, true)
|
|
{
|
|
if !resp.ok {
|
|
stderr_all.push_str(&resp.stderr);
|
|
}
|
|
stdout_all.push_str(&resp.stdout);
|
|
}
|
|
}
|
|
OptimizeStrategy::Incremental => {
|
|
if let Ok(resp) = write_commit_graph(self, false, false) {
|
|
if !resp.ok {
|
|
stderr_all.push_str(&resp.stderr);
|
|
}
|
|
stdout_all.push_str(&resp.stdout);
|
|
}
|
|
}
|
|
OptimizeStrategy::Unspecified => {}
|
|
}
|
|
|
|
Ok(OptimizeRepositoryResponse {
|
|
ok: stderr_all.is_empty(),
|
|
stdout: stdout_all,
|
|
stderr: stderr_all,
|
|
})
|
|
}
|
|
|
|
fn get_repository_statistics(&self) -> GitResult<RepositoryStatistics> {
|
|
let loose = std::fs::read_dir(self.bare_dir.join("objects"))
|
|
.map(|d| {
|
|
d.filter_map(|e| e.ok())
|
|
.filter(|e| {
|
|
e.file_type().map(|t| t.is_dir()).unwrap_or(false)
|
|
&& e.file_name().to_string_lossy().len() == 2
|
|
})
|
|
.count() as u64
|
|
})
|
|
.unwrap_or(0);
|
|
|
|
let pack_dir = self.bare_dir.join("objects").join("pack");
|
|
let pack_count = std::fs::read_dir(&pack_dir)
|
|
.map(|d| d.filter_map(|e| e.ok()).count() as u64)
|
|
.unwrap_or(0);
|
|
|
|
let cg_size = std::fs::metadata(
|
|
self.bare_dir
|
|
.join("objects")
|
|
.join("info")
|
|
.join("commit-graph"),
|
|
)
|
|
.map(|m| m.len())
|
|
.unwrap_or(0);
|
|
|
|
Ok(RepositoryStatistics {
|
|
size_bytes: 0,
|
|
loose_object_count: loose,
|
|
packed_object_count: 0,
|
|
packfile_count: pack_count,
|
|
reference_count: 0,
|
|
commit_graph_size_bytes: cg_size,
|
|
multi_pack_index_size_bytes: 0,
|
|
})
|
|
}
|
|
}
|
|
|
|
fn write_commit_graph(
|
|
gb: &GitBare,
|
|
_split: bool,
|
|
_replace: bool,
|
|
) -> GitResult<RepositoryMaintenanceResponse> {
|
|
let out = std::process::Command::new("git")
|
|
.args([
|
|
"--git-dir",
|
|
&gb.bare_dir.to_string_lossy(),
|
|
"commit-graph",
|
|
"write",
|
|
"--reachable",
|
|
])
|
|
.stdout(std::process::Stdio::piped())
|
|
.stderr(std::process::Stdio::piped())
|
|
.output()
|
|
.map_err(|e| crate::error::GitError::CommandFailed {
|
|
status_code: None,
|
|
stderr: e.to_string(),
|
|
})?;
|
|
|
|
Ok(RepositoryMaintenanceResponse {
|
|
ok: out.status.success(),
|
|
stdout: String::from_utf8_lossy(&out.stdout).into_owned(),
|
|
stderr: String::from_utf8_lossy(&out.stderr).into_owned(),
|
|
})
|
|
}
|
|
|
|
fn run_repack(
|
|
gb: &GitBare,
|
|
full: bool,
|
|
bitmaps: bool,
|
|
_midx: bool,
|
|
) -> GitResult<RepositoryMaintenanceResponse> {
|
|
let mut args = vec![
|
|
"--git-dir".to_string(),
|
|
gb.bare_dir.to_string_lossy().into_owned(),
|
|
"repack".to_string(),
|
|
];
|
|
if full {
|
|
args.push("-ad".to_string());
|
|
} else {
|
|
args.push("-d".to_string());
|
|
}
|
|
if bitmaps {
|
|
args.push("--write-bitmap-index".to_string());
|
|
}
|
|
|
|
let out = std::process::Command::new("git")
|
|
.args(&args)
|
|
.stdout(std::process::Stdio::piped())
|
|
.stderr(std::process::Stdio::piped())
|
|
.output()
|
|
.map_err(|e| crate::error::GitError::CommandFailed {
|
|
status_code: None,
|
|
stderr: e.to_string(),
|
|
})?;
|
|
|
|
Ok(RepositoryMaintenanceResponse {
|
|
ok: out.status.success(),
|
|
stdout: String::from_utf8_lossy(&out.stdout).into_owned(),
|
|
stderr: String::from_utf8_lossy(&out.stderr).into_owned(),
|
|
})
|
|
}
|
|
|
|
fn run_gc(gb: &GitBare, prune: bool, aggressive: bool) -> GitResult<RepositoryMaintenanceResponse> {
|
|
let mut args = vec![
|
|
"--git-dir".to_string(),
|
|
gb.bare_dir.to_string_lossy().into_owned(),
|
|
"gc".to_string(),
|
|
];
|
|
if prune {
|
|
args.push("--prune=now".to_string());
|
|
}
|
|
if aggressive {
|
|
args.push("--aggressive".to_string());
|
|
}
|
|
|
|
let out = std::process::Command::new("git")
|
|
.args(&args)
|
|
.stdout(std::process::Stdio::piped())
|
|
.stderr(std::process::Stdio::piped())
|
|
.output()
|
|
.map_err(|e| crate::error::GitError::CommandFailed {
|
|
status_code: None,
|
|
stderr: e.to_string(),
|
|
})?;
|
|
|
|
Ok(RepositoryMaintenanceResponse {
|
|
ok: out.status.success(),
|
|
stdout: String::from_utf8_lossy(&out.stdout).into_owned(),
|
|
stderr: String::from_utf8_lossy(&out.stderr).into_owned(),
|
|
})
|
|
}
|