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 { 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 { 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 { 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 { 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 { 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(), }) }