5ef202e
//! 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<RepoStats, crate::Error> {
let tip = self.tip_id()?;
if let Some(hit) = self.cache_get_rev::<RepoStats>("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<GcResult, crate::Error> {
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<FsckResult, crate::Error> {
let tip = self.tip_id()?;
if let Some(hit) = self.cache_get_rev::<FsckResult>("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<PackStats, crate::Error> {
let tip = self.tip_id()?;
if let Some(hit) = self.cache_get_rev::<PackStats>("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<GcResult, crate::Error> {
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
}