1use crate::config::ConfigSet;
16use crate::objects::{parse_commit, parse_tag, parse_tree, ObjectId, ObjectKind};
17use crate::repo::Repository;
18use std::collections::HashMap;
19
20#[derive(Clone, Debug, PartialEq, Eq)]
22pub struct IslandBitmap {
23 bits: Vec<u32>,
24}
25
26impl IslandBitmap {
27 fn new(size: usize) -> Self {
28 IslandBitmap {
29 bits: vec![0u32; size],
30 }
31 }
32
33 fn set(&mut self, i: u32) {
34 let block = (i / 32) as usize;
35 let mask = 1u32 << (i % 32);
36 self.bits[block] |= mask;
37 }
38
39 fn get(&self, i: u32) -> bool {
40 let block = (i / 32) as usize;
41 let mask = 1u32 << (i % 32);
42 (self.bits[block] & mask) != 0
43 }
44
45 fn or(&mut self, other: &IslandBitmap) {
46 for (a, b) in self.bits.iter_mut().zip(other.bits.iter()) {
47 *a |= *b;
48 }
49 }
50
51 fn is_subset(&self, super_: &IslandBitmap) -> bool {
53 for (s, p) in self.bits.iter().zip(super_.bits.iter()) {
54 if (s & p) != *s {
55 return false;
56 }
57 }
58 true
59 }
60
61 fn is_empty(&self) -> bool {
62 self.bits.iter().all(|&b| b == 0)
63 }
64}
65
66#[derive(Debug, Default)]
68pub struct DeltaIslands {
69 marks: HashMap<ObjectId, IslandBitmap>,
71 core_island_bit: Option<u32>,
73 active: bool,
75}
76
77struct RemoteIsland {
79 hash: u64,
80 oids: Vec<ObjectId>,
81}
82
83impl DeltaIslands {
84 #[must_use]
86 pub fn is_active(&self) -> bool {
87 self.active
88 }
89
90 #[must_use]
96 pub fn in_same_island(&self, trg: &ObjectId, src: &ObjectId) -> bool {
97 if !self.active {
98 return true;
99 }
100 let Some(trg_marks) = self.marks.get(trg) else {
101 return true;
103 };
104 let Some(src_marks) = self.marks.get(src) else {
105 return false;
107 };
108 trg_marks.is_subset(src_marks)
109 }
110
111 #[must_use]
116 pub fn delta_cmp(&self, a: &ObjectId, b: &ObjectId) -> i32 {
117 if !self.active {
118 return 0;
119 }
120 let a_marks = self.marks.get(a);
121 let b_marks = self.marks.get(b);
122
123 if let Some(am) = a_marks {
124 if b_marks.is_none_or(|bm| !am.is_subset(bm)) {
125 return -1;
126 }
127 }
128 if let Some(bm) = b_marks {
129 if a_marks.is_none_or(|am| !bm.is_subset(am)) {
130 return 1;
131 }
132 }
133 0
134 }
135
136 #[must_use]
138 pub fn is_core_object(&self, oid: &ObjectId) -> bool {
139 let Some(bit) = self.core_island_bit else {
140 return false;
141 };
142 self.marks.get(oid).is_some_and(|m| m.get(bit))
143 }
144
145 #[must_use]
147 pub fn has_core(&self) -> bool {
148 self.core_island_bit.is_some()
149 }
150}
151
152fn load_island_regexes(cfg: &ConfigSet) -> Vec<regex::Regex> {
154 cfg.get_all("pack.island")
155 .into_iter()
156 .filter_map(|v| {
157 let pat = if v.starts_with('^') {
158 v
159 } else {
160 format!("^{v}")
161 };
162 regex::Regex::new(&pat).ok()
163 })
164 .collect()
165}
166
167fn island_name_for_ref(regexes: &[regex::Regex], ref_name: &str) -> Option<String> {
170 for rx in regexes.iter().rev() {
172 if let Some(caps) = rx.captures(ref_name) {
173 let mut name = String::new();
174 for m in caps.iter().skip(1).flatten() {
175 if m.as_str().is_empty() {
176 continue;
177 }
178 if !name.is_empty() {
179 name.push('-');
180 }
181 name.push_str(m.as_str());
182 }
183 return Some(name);
184 }
185 }
186 None
187}
188
189pub fn load_delta_islands(
195 repo: &Repository,
196 cfg: &ConfigSet,
197 packed: &std::collections::HashSet<ObjectId>,
198) -> DeltaIslands {
199 let regexes = load_island_regexes(cfg);
200 if regexes.is_empty() {
201 return DeltaIslands::default();
202 }
203 let core_name = cfg.get("pack.islandcore");
204
205 let mut by_name: HashMap<String, RemoteIsland> = HashMap::new();
207 let refs = crate::refs::list_refs(&repo.git_dir, "refs/").unwrap_or_default();
208 for (ref_name, oid) in &refs {
209 let Some(name) = island_name_for_ref(®exes, ref_name) else {
210 continue;
211 };
212 let entry = by_name.entry(name).or_insert_with(|| RemoteIsland {
213 hash: 0,
214 oids: Vec::new(),
215 });
216 entry.oids.push(*oid);
217 let b = oid.as_bytes();
220 let core = u64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]);
221 entry.hash = entry.hash.wrapping_add(core);
222 }
223
224 if by_name.is_empty() {
225 return DeltaIslands::default();
226 }
227
228 let core_hash = core_name
230 .as_deref()
231 .and_then(|n| by_name.get(n))
232 .map(|ri| ri.hash);
233
234 let mut islands: Vec<RemoteIsland> = by_name.into_values().collect();
237 islands.sort_by(|a, b| a.hash.cmp(&b.hash));
240 let mut deduped: Vec<RemoteIsland> = Vec::new();
241 for isl in islands {
242 if deduped.iter().any(|d| d.hash == isl.hash) {
243 continue;
244 }
245 deduped.push(isl);
246 }
247
248 let island_count = deduped.len();
249 let bitmap_size = island_count / 32 + 1;
250
251 let mut islands_obj = DeltaIslands {
252 marks: HashMap::new(),
253 core_island_bit: None,
254 active: false,
255 };
256
257 for (bit, isl) in deduped.iter().enumerate() {
259 let bit = bit as u32;
260 if core_hash == Some(isl.hash) {
261 islands_obj.core_island_bit = Some(bit);
262 }
263 for oid in &isl.oids {
264 let marks = islands_obj
265 .marks
266 .entry(*oid)
267 .or_insert_with(|| IslandBitmap::new(bitmap_size));
268 marks.set(bit);
269 }
270 }
271
272 islands_obj.active = true;
273
274 propagate_marks(repo, &mut islands_obj, packed, bitmap_size);
276
277 islands_obj
278}
279
280fn add_marks(
282 table: &mut HashMap<ObjectId, IslandBitmap>,
283 oid: &ObjectId,
284 marks: &IslandBitmap,
285 bitmap_size: usize,
286) {
287 let entry = table
288 .entry(*oid)
289 .or_insert_with(|| IslandBitmap::new(bitmap_size));
290 entry.or(marks);
291}
292
293fn propagate_marks(
295 repo: &Repository,
296 islands: &mut DeltaIslands,
297 packed: &std::collections::HashSet<ObjectId>,
298 bitmap_size: usize,
299) {
300 let tag_oids: Vec<ObjectId> = islands
302 .marks
303 .keys()
304 .copied()
305 .filter(|oid| matches!(repo.odb.read(oid).map(|o| o.kind), Ok(ObjectKind::Tag)))
306 .collect();
307 for tag_oid in tag_oids {
308 let marks = islands.marks.get(&tag_oid).cloned();
309 let Some(marks) = marks else { continue };
310 let mut cur = tag_oid;
311 while let Ok(obj) = repo.odb.read(&cur) {
312 if obj.kind != ObjectKind::Tag {
313 break;
314 }
315 let Ok(tag) = parse_tag(&obj.data) else { break };
316 add_marks(&mut islands.marks, &tag.object, &marks, bitmap_size);
317 cur = tag.object;
318 }
319 }
320
321 let mut commit_marks: Vec<(ObjectId, IslandBitmap)> = islands
326 .marks
327 .iter()
328 .filter(|(oid, _)| matches!(repo.odb.read(oid).map(|o| o.kind), Ok(ObjectKind::Commit)))
329 .map(|(oid, m)| (*oid, m.clone()))
330 .collect();
331
332 let mut tree_roots: HashMap<ObjectId, IslandBitmap> = HashMap::new();
333 let mut visited: std::collections::HashSet<ObjectId> = std::collections::HashSet::new();
334 while let Some((cid, _)) = commit_marks.pop() {
335 let Some(marks) = islands.marks.get(&cid).cloned() else {
337 continue;
338 };
339 let Ok(obj) = repo.odb.read(&cid) else {
340 continue;
341 };
342 if obj.kind != ObjectKind::Commit {
343 continue;
344 }
345 let Ok(commit) = parse_commit(&obj.data) else {
346 continue;
347 };
348 add_marks(&mut tree_roots, &commit.tree, &marks, bitmap_size);
350 add_marks(&mut islands.marks, &commit.tree, &marks, bitmap_size);
351 for parent in &commit.parents {
353 let before = islands.marks.get(parent).cloned();
354 add_marks(&mut islands.marks, parent, &marks, bitmap_size);
355 let after = islands.marks.get(parent).cloned();
356 if before != after || !visited.contains(parent) {
357 visited.insert(*parent);
358 let pm = islands
359 .marks
360 .get(parent)
361 .cloned()
362 .unwrap_or_else(|| IslandBitmap::new(bitmap_size));
363 commit_marks.push((*parent, pm));
364 }
365 }
366 }
367
368 let mut tree_queue: Vec<(ObjectId, IslandBitmap)> = tree_roots.into_iter().collect();
372 let mut tree_visited: std::collections::HashSet<ObjectId> = std::collections::HashSet::new();
373 while let Some((tid, _)) = tree_queue.pop() {
374 let Some(marks) = islands.marks.get(&tid).cloned() else {
375 continue;
376 };
377 tree_visited.insert(tid);
380 let Ok(obj) = repo.odb.read(&tid) else {
381 continue;
382 };
383 if obj.kind != ObjectKind::Tree {
384 continue;
385 }
386 let Ok(entries) = parse_tree(&obj.data) else {
387 continue;
388 };
389 for ent in entries {
390 if ent.mode == crate::index::MODE_GITLINK {
392 continue;
393 }
394 let before = islands.marks.get(&ent.oid).cloned();
395 add_marks(&mut islands.marks, &ent.oid, &marks, bitmap_size);
396 let after = islands.marks.get(&ent.oid).cloned();
397 if ent.mode == 0o040000 && (before != after || !tree_visited.contains(&ent.oid)) {
399 let em = islands
400 .marks
401 .get(&ent.oid)
402 .cloned()
403 .unwrap_or_else(|| IslandBitmap::new(bitmap_size));
404 tree_queue.push((ent.oid, em));
405 }
406 }
407 }
408
409 islands
412 .marks
413 .retain(|oid, m| !m.is_empty() && packed.contains(oid));
414}