use std::path::Path;
use crate::error::{Error, Result};
use crate::pack::read_local_pack_indexes;
#[derive(Debug, Clone)]
pub struct GeometricPack {
pub stem: String,
pub object_count: usize,
pub mtime_secs: u64,
}
#[must_use]
pub fn compute_geometry_split(weights: &[usize], split_factor: i32) -> usize {
let sf = split_factor.max(1) as u64;
let pack_nr = weights.len();
if pack_nr == 0 {
return 0;
}
let mut split = 0usize;
let mut found = false;
for idx in (1..pack_nr).rev() {
let ours = weights[idx] as u64;
let prev = weights[idx - 1] as u64;
if ours < sf.saturating_mul(prev) {
split = idx;
found = true;
break;
}
}
if found {
split += 1;
}
let mut total_size: u64 = 0;
for j in 0..split {
total_size = total_size.saturating_add(weights[j] as u64);
}
let mut j = split;
while j < pack_nr {
let ours = weights[j] as u64;
if ours < sf.saturating_mul(total_size) {
split += 1;
total_size = total_size.saturating_add(ours);
j += 1;
} else {
break;
}
}
split
}
pub fn collect_geometry_packs(
objects_dir: &Path,
pack_kept_objects: bool,
keep_pack_names: &[String],
) -> Result<Vec<GeometricPack>> {
let pack_dir = objects_dir.join("pack");
let indexes = read_local_pack_indexes(objects_dir)?;
let mut out = Vec::new();
for idx in indexes {
let pack_name = idx
.pack_path
.file_name()
.and_then(|s| s.to_str())
.map(str::to_owned)
.ok_or_else(|| Error::CorruptObject("invalid pack path".to_owned()))?;
if !pack_name.starts_with("pack-") || !pack_name.ends_with(".pack") {
continue;
}
if keep_pack_names.iter().any(|k| {
k == &pack_name
|| k.strip_prefix("pack/").unwrap_or(k.as_str()) == pack_name
|| Path::new(k).file_name().and_then(|s| s.to_str()) == Some(pack_name.as_str())
}) {
continue;
}
let stem = pack_name
.strip_suffix(".pack")
.unwrap_or(pack_name.as_str())
.to_string();
let keep_path = pack_dir.join(format!("{stem}.keep"));
if keep_path.is_file() && !pack_kept_objects {
continue;
}
if pack_dir.join(format!("{stem}.promisor")).is_file() {
continue;
}
let mtime_secs = std::fs::metadata(&idx.pack_path)
.map(|m| {
m.modified()
.ok()
.and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
.map(|d| d.as_secs())
.unwrap_or(0)
})
.unwrap_or(0);
out.push(GeometricPack {
stem,
object_count: idx.entries.len(),
mtime_secs,
});
}
out.sort_by(|a, b| a.object_count.cmp(&b.object_count));
Ok(out)
}
pub fn collect_promisor_geometry_packs(
objects_dir: &Path,
pack_kept_objects: bool,
keep_pack_names: &[String],
) -> Result<Vec<GeometricPack>> {
let pack_dir = objects_dir.join("pack");
let indexes = read_local_pack_indexes(objects_dir)?;
let mut out = Vec::new();
for idx in indexes {
let pack_name = idx
.pack_path
.file_name()
.and_then(|s| s.to_str())
.map(str::to_owned)
.ok_or_else(|| Error::CorruptObject("invalid pack path".to_owned()))?;
if !pack_name.starts_with("pack-") || !pack_name.ends_with(".pack") {
continue;
}
if keep_pack_names.iter().any(|k| {
k == &pack_name
|| k.strip_prefix("pack/").unwrap_or(k.as_str()) == pack_name
|| Path::new(k).file_name().and_then(|s| s.to_str()) == Some(pack_name.as_str())
}) {
continue;
}
let stem = pack_name
.strip_suffix(".pack")
.unwrap_or(pack_name.as_str())
.to_string();
let keep_path = pack_dir.join(format!("{stem}.keep"));
if keep_path.is_file() && !pack_kept_objects {
continue;
}
if !pack_dir.join(format!("{stem}.promisor")).is_file() {
continue;
}
let mtime_secs = std::fs::metadata(&idx.pack_path)
.map(|m| {
m.modified()
.ok()
.and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
.map(|d| d.as_secs())
.unwrap_or(0)
})
.unwrap_or(0);
out.push(GeometricPack {
stem,
object_count: idx.entries.len(),
mtime_secs,
});
}
out.sort_by(|a, b| a.object_count.cmp(&b.object_count));
Ok(out)
}
#[must_use]
pub fn preferred_pack_stem_after_split(packs: &[GeometricPack], split: usize) -> Option<String> {
if split >= packs.len() {
return None;
}
packs.last().map(|p| p.stem.clone())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn progression_intact_split_is_zero() {
let w = vec![3, 6, 12];
assert_eq!(compute_geometry_split(&w, 2), 0);
}
#[test]
fn duplicate_small_packs_roll_up() {
let w = vec![3, 3, 6];
assert_eq!(compute_geometry_split(&w, 2), 3);
}
}