//! gc usecase: repository maintenance. use crate::GitRepository; use crate::gc::impls::GcOps; use crate::gc::types::{FsckResult, GcResult, PackStats, RepoStats}; impl GcOps for GitRepository { fn gc_stats(&self) -> Result { let tip = self.tip_id()?; if let Some(hit) = self.cache_get_rev::("gc", "stats", "stats", tip) { return Ok(hit); } let repo = self.repo()?; let loose = walk_loose_objects(&repo); let v = RepoStats { size: 0, git_size: 0, lfs_size: 0, object_count: loose, loose_object_count: loose, pack_count: 0, }; self.cache_put_rev("gc", "stats", "stats", tip, &v); Ok(v) } fn gc_gc(&self) -> Result { let repo = self.repo()?; let path = repo.path().to_path_buf(); crate::gitcli::run(&path, &["gc", "--quiet"])?; tracing::info!("gc pass completed via git gc"); Ok(GcResult { pruned: 0, packed: 0, message: "gc pass complete".into(), }) } fn gc_fsck(&self) -> Result { let tip = self.tip_id()?; if let Some(hit) = self.cache_get_rev::("gc", "fsck", "fsck", tip) { return Ok(hit); } let repo = self.repo()?; let mut errors = Vec::new(); let mut validated = 0u64; match repo.objects.iter() { Ok(all) => { for id in all { let id = match id { Ok(id) => id, Err(e) => { errors.push(format!("odb iteration error: {e}")); continue; } }; match repo.find_object(id) { Ok(obj) => { // Verify the object's checksum matches its id. let kind = obj.kind; let data = obj.data.clone(); let computed = gix::objs::Write::write_buf(&repo, kind, &data) .map_err(|e| crate::Error::Odb(e.to_string()))?; if computed != id { errors.push(format!("object {id} failed checksum verification")); } validated += 1; } Err(e) => { errors.push(format!("object {id} unreadable: {e}")); } } } } Err(e) => errors.push(format!("failed to enumerate objects: {e}")), } tracing::info!(validated, error_count = errors.len(), "gc_fsck completed"); let v = FsckResult { ok: errors.is_empty(), errors, }; self.cache_put_rev("gc", "fsck", "fsck", tip, &v); Ok(v) } fn gc_count_objects(&self) -> Result { let tip = self.tip_id()?; if let Some(hit) = self.cache_get_rev::("gc", "objcount", "objcount", tip) { return Ok(hit); } let repo = self.repo()?; let loose = walk_loose_objects(&repo); let v = PackStats { objects: loose, size: 0, delta_count: 0, }; self.cache_put_rev("gc", "objcount", "objcount", tip, &v); Ok(v) } fn gc_prune_orphans(&self) -> Result { let repo = self.repo()?; let path = repo.path().to_path_buf(); crate::gitcli::run(&path, &["prune"])?; tracing::info!("prune pass completed via git prune"); Ok(GcResult { pruned: 0, packed: 0, message: "prune pass complete".into(), }) } } /// Walk the loose-objects directory and count files. fn walk_loose_objects(repo: &gix::Repository) -> u64 { let mut count = 0u64; let dir = repo.path().join("objects"); let Ok(read) = std::fs::read_dir(dir) else { return 0; }; for shelf in read.flatten() { let Ok(entries) = std::fs::read_dir(shelf.path()) else { continue; }; for entry in entries.flatten() { if entry.path().is_file() { count += 1; } } } count }