grit_lib/
pack_geometry.rs1use std::path::Path;
8
9use crate::error::{Error, Result};
10use crate::pack::read_local_pack_indexes;
11
12#[derive(Debug, Clone)]
14pub struct GeometricPack {
15 pub stem: String,
17 pub object_count: usize,
19 pub mtime_secs: u64,
21}
22
23#[must_use]
25pub fn compute_geometry_split(weights: &[usize], split_factor: i32) -> usize {
26 let sf = split_factor.max(1) as u64;
27 let pack_nr = weights.len();
28 if pack_nr == 0 {
29 return 0;
30 }
31
32 let mut split = 0usize;
35 let mut found = false;
36 for idx in (1..pack_nr).rev() {
37 let ours = weights[idx] as u64;
38 let prev = weights[idx - 1] as u64;
39 if ours < sf.saturating_mul(prev) {
40 split = idx;
41 found = true;
42 break;
43 }
44 }
45 if found {
46 split += 1;
47 }
48
49 let mut total_size: u64 = 0;
50 for j in 0..split {
51 total_size = total_size.saturating_add(weights[j] as u64);
52 }
53
54 let mut j = split;
55 while j < pack_nr {
56 let ours = weights[j] as u64;
57 if ours < sf.saturating_mul(total_size) {
58 split += 1;
59 total_size = total_size.saturating_add(ours);
60 j += 1;
61 } else {
62 break;
63 }
64 }
65
66 split
67}
68
69pub fn collect_geometry_packs(
74 objects_dir: &Path,
75 pack_kept_objects: bool,
76 keep_pack_names: &[String],
77) -> Result<Vec<GeometricPack>> {
78 let pack_dir = objects_dir.join("pack");
79 let indexes = read_local_pack_indexes(objects_dir)?;
80 let mut out = Vec::new();
81
82 for idx in indexes {
83 let pack_name = idx
84 .pack_path
85 .file_name()
86 .and_then(|s| s.to_str())
87 .map(str::to_owned)
88 .ok_or_else(|| Error::CorruptObject("invalid pack path".to_owned()))?;
89
90 if !pack_name.starts_with("pack-") || !pack_name.ends_with(".pack") {
91 continue;
92 }
93
94 if keep_pack_names.iter().any(|k| {
95 k == &pack_name
96 || k.strip_prefix("pack/").unwrap_or(k.as_str()) == pack_name
97 || Path::new(k).file_name().and_then(|s| s.to_str()) == Some(pack_name.as_str())
98 }) {
99 continue;
100 }
101
102 let stem = pack_name
103 .strip_suffix(".pack")
104 .unwrap_or(pack_name.as_str())
105 .to_string();
106
107 let keep_path = pack_dir.join(format!("{stem}.keep"));
108 if keep_path.is_file() && !pack_kept_objects {
109 continue;
110 }
111
112 if pack_dir.join(format!("{stem}.promisor")).is_file() {
113 continue;
114 }
115
116 let mtime_secs = std::fs::metadata(&idx.pack_path)
117 .map(|m| {
118 m.modified()
119 .ok()
120 .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
121 .map(|d| d.as_secs())
122 .unwrap_or(0)
123 })
124 .unwrap_or(0);
125
126 out.push(GeometricPack {
127 stem,
128 object_count: idx.entries.len(),
129 mtime_secs,
130 });
131 }
132
133 out.sort_by(|a, b| a.object_count.cmp(&b.object_count));
134 Ok(out)
135}
136
137pub fn collect_promisor_geometry_packs(
139 objects_dir: &Path,
140 pack_kept_objects: bool,
141 keep_pack_names: &[String],
142) -> Result<Vec<GeometricPack>> {
143 let pack_dir = objects_dir.join("pack");
144 let indexes = read_local_pack_indexes(objects_dir)?;
145 let mut out = Vec::new();
146
147 for idx in indexes {
148 let pack_name = idx
149 .pack_path
150 .file_name()
151 .and_then(|s| s.to_str())
152 .map(str::to_owned)
153 .ok_or_else(|| Error::CorruptObject("invalid pack path".to_owned()))?;
154
155 if !pack_name.starts_with("pack-") || !pack_name.ends_with(".pack") {
156 continue;
157 }
158
159 if keep_pack_names.iter().any(|k| {
160 k == &pack_name
161 || k.strip_prefix("pack/").unwrap_or(k.as_str()) == pack_name
162 || Path::new(k).file_name().and_then(|s| s.to_str()) == Some(pack_name.as_str())
163 }) {
164 continue;
165 }
166
167 let stem = pack_name
168 .strip_suffix(".pack")
169 .unwrap_or(pack_name.as_str())
170 .to_string();
171
172 let keep_path = pack_dir.join(format!("{stem}.keep"));
173 if keep_path.is_file() && !pack_kept_objects {
174 continue;
175 }
176
177 if !pack_dir.join(format!("{stem}.promisor")).is_file() {
178 continue;
179 }
180
181 let mtime_secs = std::fs::metadata(&idx.pack_path)
182 .map(|m| {
183 m.modified()
184 .ok()
185 .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
186 .map(|d| d.as_secs())
187 .unwrap_or(0)
188 })
189 .unwrap_or(0);
190
191 out.push(GeometricPack {
192 stem,
193 object_count: idx.entries.len(),
194 mtime_secs,
195 });
196 }
197
198 out.sort_by(|a, b| a.object_count.cmp(&b.object_count));
199 Ok(out)
200}
201
202#[must_use]
204pub fn preferred_pack_stem_after_split(packs: &[GeometricPack], split: usize) -> Option<String> {
205 if split >= packs.len() {
206 return None;
207 }
208 packs.last().map(|p| p.stem.clone())
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215
216 #[test]
217 fn progression_intact_split_is_zero() {
218 let w = vec![3, 6, 12];
220 assert_eq!(compute_geometry_split(&w, 2), 0);
221 }
222
223 #[test]
224 fn duplicate_small_packs_roll_up() {
225 let w = vec![3, 3, 6];
227 assert_eq!(compute_geometry_split(&w, 2), 3);
228 }
229}