use crate::core::{find_repo_root, Object, ObjectHash};
use crate::response::GcResponse;
use crate::storage::ObjectStore;
use std::fs;
pub use crate::storage::pack::{
load_pack_index, read_pack_object, write_pack, write_pack_index, PackIndexEntry, INDEX_MAGIC,
INDEX_VERSION, PACK_MAGIC, PACK_VERSION,
};
pub fn execute() -> Result<GcResponse, crate::errors::LitError> {
let repo_root = find_repo_root()?;
let store = ObjectStore::new(&repo_root);
let all_hashes = store
.list()
.map_err(|e| format!("Failed to list objects: {}", e))?;
if all_hashes.is_empty() {
return Ok(GcResponse {
objects_packed: 0,
packs_created: 0,
loose_removed: 0,
bytes_saved: 0,
message: "No objects to pack".to_string(),
});
}
let mut objects: Vec<(ObjectHash, Object)> = Vec::new();
let mut total_loose_bytes: u64 = 0;
for hash in &all_hashes {
let obj = store.read(hash)?;
let loose_path = repo_root
.join(".lit")
.join("objects")
.join(&hash.as_str()[..4])
.join(&hash.as_str()[4..]);
if let Ok(meta) = fs::metadata(&loose_path) {
total_loose_bytes += meta.len();
}
objects.push((hash.clone(), obj));
}
let packs_dir = repo_root.join(".lit").join("packs");
fs::create_dir_all(&packs_dir)
.map_err(|e| format!("Failed to create packs directory: {}", e))?;
let pack_name = format!("pack-{}", chrono::Utc::now().format("%Y%m%d%H%M%S"));
let pack_path = packs_dir.join(format!("{}.pack", pack_name));
let index_path = packs_dir.join(format!("{}.idx", pack_name));
let encryption = store.encryption();
let index_entries = {
let encryption = encryption
.lock()
.map_err(|e| format!("Failed to acquire encryption lock: {}", e))?;
write_pack(&objects, &pack_path, &encryption)?
};
write_pack_index(&index_entries, &index_path)?;
let pack_bytes = fs::metadata(&pack_path).map(|m| m.len()).unwrap_or(0);
let index_bytes = fs::metadata(&index_path).map(|m| m.len()).unwrap_or(0);
let mut loose_removed = 0u64;
for hash in &all_hashes {
let dir = hash.as_str()[..4].to_string();
let file = hash.as_str()[4..].to_string();
let loose_path = repo_root
.join(".lit")
.join("objects")
.join(&dir)
.join(&file);
if loose_path.exists() {
if fs::remove_file(&loose_path).is_ok() {
loose_removed += 1;
}
let shard_dir = repo_root.join(".lit").join("objects").join(&dir);
if let Ok(mut entries) = fs::read_dir(&shard_dir) {
if entries.next().is_none() {
let _ = fs::remove_dir(&shard_dir);
}
}
}
}
let bytes_saved = if total_loose_bytes > (pack_bytes + index_bytes) {
total_loose_bytes - pack_bytes - index_bytes
} else {
0
};
Ok(GcResponse {
objects_packed: objects.len() as u64,
packs_created: 1,
loose_removed,
bytes_saved,
message: format!(
"Packed {} objects into {} ({} bytes saved)",
objects.len(),
pack_path.display(),
bytes_saved
),
})
}