1use crate::environment::RepositoryOptions;
7
8use std::borrow::Cow;
9use std::collections::{HashMap, HashSet, VecDeque};
10use std::io::Write;
11use std::path::Path;
12
13use flate2::write::ZlibEncoder;
14use flate2::Compression;
15
16use crate::bitmap_walk::ReachableSet;
17use crate::delta_encode::{encode_prefix_extension_delta, DeltaIndex};
18use crate::delta_islands::DeltaIslands;
19use crate::error::{Error, Result};
20use crate::objects::{
21 empty_object_id_hash_set, oid_hash_map, oid_hash_set, oid_hash_set_from, parse_commit,
22 parse_tag, HashAlgo, Object, ObjectId, ObjectInfo, ObjectKind, OidHashMap, OidHashSet,
23 TreeRefIter,
24};
25use crate::odb::Odb;
26use crate::pack_bitmap::BitmapIndex;
27use crate::pack_name_hash::pack_name_hash;
28use crate::pack_object_select::{HaveBoundary, NameTrees};
29use crate::parallel::{try_par_map_force, ParallelHashError};
30use crate::repo::Repository;
31use crate::rev_list::ObjectFilter;
32
33#[derive(Clone, Copy, Debug)]
35pub struct PackBuildOptions {
36 pub thin: bool,
39 pub delta: bool,
41 pub window: usize,
43 pub max_depth: usize,
45 pub use_ofs_delta: bool,
47 pub respect_islands: bool,
49 pub reuse_deltas: bool,
51 pub reuse_objects: bool,
53 pub use_bitmaps: bool,
55}
56
57impl Default for PackBuildOptions {
58 fn default() -> Self {
59 Self {
60 thin: false,
61 delta: false,
62 window: 10,
63 max_depth: 50,
64 use_ofs_delta: true,
65 respect_islands: false,
66 reuse_deltas: true,
67 reuse_objects: true,
68 use_bitmaps: true,
69 }
70 }
71}
72
73impl PackBuildOptions {
74 #[must_use]
76 pub fn window_and_depth_from_config(cfg: Option<&crate::config::ConfigSet>) -> (usize, usize) {
77 cfg.map(|c| (c.pack_object_window(), c.pack_object_depth()))
78 .unwrap_or((10, crate::pack::DEFAULT_PACK_DEPTH))
79 }
80
81 #[must_use]
83 pub fn for_local_copy(cfg: Option<&crate::config::ConfigSet>) -> Self {
84 let (window, max_depth) = Self::window_and_depth_from_config(cfg);
85 Self {
86 thin: false,
87 delta: true,
88 window,
89 max_depth,
90 use_ofs_delta: true,
91 reuse_deltas: max_depth > 0,
92 reuse_objects: true,
93 use_bitmaps: true,
94 ..Default::default()
95 }
96 }
97
98 #[must_use]
101 pub fn for_repack_rewrite(cfg: Option<&crate::config::ConfigSet>) -> Self {
102 let max_depth = Self::window_and_depth_from_config(cfg).1;
103 Self {
104 thin: false,
105 delta: true,
106 window: 0,
107 max_depth,
108 use_ofs_delta: true,
109 reuse_deltas: max_depth > 0,
110 reuse_objects: true,
111 use_bitmaps: true,
112 ..Default::default()
113 }
114 }
115
116 #[must_use]
131 pub fn for_serve(
132 cfg: Option<&crate::config::ConfigSet>,
133 thin: bool,
134 use_ofs_delta: bool,
135 ) -> Self {
136 let (window, max_depth) = Self::window_and_depth_from_config(cfg);
137 Self {
138 thin,
139 delta: true,
140 window,
141 max_depth,
142 use_ofs_delta,
143 reuse_deltas: true,
144 reuse_objects: true,
145 use_bitmaps: true,
146 ..Default::default()
147 }
148 }
149
150 #[must_use]
155 pub fn for_local_push(cfg: Option<&crate::config::ConfigSet>) -> Self {
156 let (window, max_depth) = Self::window_and_depth_from_config(cfg);
157 Self {
158 thin: true,
159 delta: true,
160 window,
161 max_depth,
162 use_ofs_delta: true,
163 reuse_deltas: true,
164 reuse_objects: true,
165 use_bitmaps: true,
166 ..Default::default()
167 }
168 }
169}
170
171#[derive(Clone, Copy, Debug, Default)]
173pub struct PackObjectsOptions {
174 pub build: PackBuildOptions,
176 pub search_threads: Option<usize>,
178 pub write_threads: Option<usize>,
180}
181
182impl From<PackBuildOptions> for PackObjectsOptions {
183 fn from(build: PackBuildOptions) -> Self {
184 Self {
185 build,
186 search_threads: None,
187 write_threads: None,
188 }
189 }
190}
191
192#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
194pub struct PackStats {
195 pub object_count: u32,
197 pub pack_bytes: u64,
199 pub delta_count: u32,
201 pub verbatim_reuse_bytes: u64,
203 pub verbatim_reuse_objects: u32,
205 pub pack_ofs_fixup_objects: u32,
207 pub pack_ref_from_ofs_objects: u32,
209}
210
211#[derive(Clone, Copy, Debug, PartialEq, Eq)]
213pub enum PackProgress {
214 Enumerated {
216 total: usize,
218 },
219 PlannedDeltas,
221 WroteObject {
223 written: usize,
225 total: usize,
227 },
228}
229
230pub struct PackObjects<'a> {
232 odb: &'a Odb,
233 source_odb: &'a Odb,
234 opts: PackObjectsOptions,
235 wants: Vec<ObjectId>,
236 haves: Vec<ObjectId>,
237 have_shallow: OidHashSet,
238 source_shallow: OidHashSet,
239 progress: Option<Box<dyn FnMut(PackProgress)>>,
240}
241
242impl<'a> PackObjects<'a> {
243 #[must_use]
245 pub fn new(odb: &'a Odb, opts: PackObjectsOptions) -> Self {
246 Self {
247 odb,
248 source_odb: odb,
249 opts,
250 wants: Vec::new(),
251 haves: Vec::new(),
252 have_shallow: oid_hash_set(),
253 source_shallow: oid_hash_set(),
254 progress: None,
255 }
256 }
257
258 #[must_use]
260 pub fn with_source_odb(mut self, source: &'a Odb) -> Self {
261 self.source_odb = source;
262 self
263 }
264
265 #[must_use]
267 pub fn wants(mut self, wants: &[ObjectId]) -> Self {
268 self.wants.extend_from_slice(wants);
269 self
270 }
271
272 #[must_use]
274 pub fn haves(mut self, haves: &[ObjectId]) -> Self {
275 self.haves.extend_from_slice(haves);
276 self
277 }
278
279 #[must_use]
281 pub fn have_shallow_grafts(mut self, grafts: &HashSet<ObjectId>) -> Self {
282 self.have_shallow = grafts.iter().copied().collect();
283 self
284 }
285
286 #[must_use]
288 pub fn source_shallow_grafts(mut self, grafts: &HashSet<ObjectId>) -> Self {
289 self.source_shallow = grafts.iter().copied().collect();
290 self
291 }
292
293 #[must_use]
295 pub fn progress<F>(mut self, f: F) -> Self
296 where
297 F: FnMut(PackProgress) + 'static,
298 {
299 self.progress = Some(Box::new(f));
300 self
301 }
302
303 fn fire(&mut self, ev: PackProgress) {
304 if let Some(ref mut f) = self.progress {
305 f(ev);
306 }
307 }
308
309 pub fn write_to(mut self, w: &mut dyn Write) -> Result<PackStats> {
315 let opts = self.opts.build;
316 let have_shallow: HashSet<ObjectId> = self.have_shallow.iter().copied().collect();
317 let source_shallow: HashSet<ObjectId> = self.source_shallow.iter().copied().collect();
318 let peer_odb = (!std::ptr::eq(self.source_odb, self.odb)).then_some(self.odb);
319 let selection = crate::pack_object_select::enumerate_pack_objects_naming(
320 self.source_odb,
321 &self.wants,
322 &self.haves,
323 &crate::pack_object_select::PackEnumerateOptions {
324 filter: None,
325 shallow_grafts: &source_shallow,
326 have_shallow_grafts: Some(&have_shallow),
327 use_bitmaps: opts.use_bitmaps,
328 exclude_objects: None,
329 peer_odb,
330 have_boundary: HaveBoundary::CommitClosure,
331 },
332 NameTrees::for_delta_search(&opts),
333 )?;
334 let send = selection.send;
335 self.fire(PackProgress::Enumerated { total: send.len() });
336 if !opts.delta {
337 let bytes = serialize_pack(self.source_odb, &send, &opts)?;
338 w.write_all(&bytes).map_err(Error::Io)?;
339 let count = u32::from_be_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
340 return Ok(PackStats {
341 object_count: count,
342 pack_bytes: bytes.len() as u64,
343 ..Default::default()
344 });
345 }
346 let plan = plan_deltas(
347 self.source_odb,
348 &send,
349 &selection.have_closure,
350 selection.peer_held.as_deref(),
351 &opts,
352 &selection.name_hashes,
353 self.opts.search_threads,
354 )?;
355 self.fire(PackProgress::PlannedDeltas);
356 crate::pack_delta::write_pack_with_deltas(
357 self.source_odb,
358 &plan.entries,
359 &opts,
360 self.opts.write_threads,
361 w,
362 None,
363 )
364 }
365}
366
367pub fn reachable_closure(
370 odb: &Odb,
371 roots: &[ObjectId],
372 stop: &HashSet<ObjectId>,
373 skip_missing: bool,
374 shallow_grafts: &HashSet<ObjectId>,
375) -> Result<HashSet<ObjectId>> {
376 let stop = oid_hash_set_from(stop);
377 let shallow_grafts = oid_hash_set_from(shallow_grafts);
378 let mut seen = oid_hash_set();
379 let order = collect_reachable_excluding(odb, roots, &stop, skip_missing, &shallow_grafts)?;
380 for oid in order {
381 seen.insert(oid);
382 }
383 Ok(seen.into_iter().collect())
384}
385
386pub fn build_pack(
389 odb: &Odb,
390 wants: &[ObjectId],
391 haves: &[ObjectId],
392 opts: &PackBuildOptions,
393) -> Result<Vec<u8>> {
394 build_pack_with_shallow_filter_and_boundary(
395 odb,
396 wants,
397 haves,
398 empty_object_id_hash_set(),
399 empty_object_id_hash_set(),
400 None,
401 opts,
402 HaveBoundary::CommitClosure,
403 )
404}
405
406pub fn build_pack_for_push(
415 odb: &Odb,
416 wants: &[ObjectId],
417 haves: &[ObjectId],
418 opts: &PackBuildOptions,
419) -> Result<Vec<u8>> {
420 build_pack_with_shallow_filter_and_boundary(
421 odb,
422 wants,
423 haves,
424 empty_object_id_hash_set(),
425 empty_object_id_hash_set(),
426 None,
427 opts,
428 HaveBoundary::TipsOnly,
429 )
430}
431
432pub fn build_pack_with_shallow_and_filter(
437 odb: &Odb,
438 wants: &[ObjectId],
439 haves: &[ObjectId],
440 have_shallow: &HashSet<ObjectId>,
441 source_shallow: &HashSet<ObjectId>,
442 filter: Option<&ObjectFilter>,
443 opts: &PackBuildOptions,
444) -> Result<Vec<u8>> {
445 build_pack_with_shallow_filter_and_boundary(
446 odb,
447 wants,
448 haves,
449 have_shallow,
450 source_shallow,
451 filter,
452 opts,
453 HaveBoundary::CommitClosure,
454 )
455}
456
457#[allow(clippy::too_many_arguments)]
458fn build_pack_with_shallow_filter_and_boundary(
459 odb: &Odb,
460 wants: &[ObjectId],
461 haves: &[ObjectId],
462 have_shallow: &HashSet<ObjectId>,
463 source_shallow: &HashSet<ObjectId>,
464 filter: Option<&ObjectFilter>,
465 opts: &PackBuildOptions,
466 have_boundary: HaveBoundary,
467) -> Result<Vec<u8>> {
468 let mut pack = Vec::new();
469 write_pack_with_shallow_and_filter(
470 odb,
471 wants,
472 haves,
473 have_shallow,
474 source_shallow,
475 filter,
476 opts,
477 have_boundary,
478 &mut pack,
479 )?;
480 Ok(pack)
481}
482
483#[allow(clippy::too_many_arguments)]
485fn write_pack_with_shallow_and_filter(
486 odb: &Odb,
487 wants: &[ObjectId],
488 haves: &[ObjectId],
489 have_shallow: &HashSet<ObjectId>,
490 source_shallow: &HashSet<ObjectId>,
491 filter: Option<&ObjectFilter>,
492 opts: &PackBuildOptions,
493 have_boundary: HaveBoundary,
494 w: &mut dyn Write,
495) -> Result<PackStats> {
496 let selection = crate::pack_object_select::enumerate_pack_objects_naming(
497 odb,
498 wants,
499 haves,
500 &crate::pack_object_select::PackEnumerateOptions {
501 filter,
502 shallow_grafts: source_shallow,
503 have_shallow_grafts: Some(have_shallow),
504 use_bitmaps: opts.use_bitmaps,
505 exclude_objects: None,
506 peer_odb: None,
507 have_boundary,
508 },
509 NameTrees::for_delta_search(opts),
510 )?;
511 if !opts.delta {
512 let bytes = serialize_pack(odb, &selection.send, opts)?;
513 w.write_all(&bytes).map_err(Error::Io)?;
514 return Ok(PackStats {
515 object_count: u32::try_from(selection.send.len()).unwrap_or(u32::MAX),
516 pack_bytes: bytes.len() as u64,
517 ..PackStats::default()
518 });
519 }
520 let plan = plan_deltas(
521 odb,
522 &selection.send,
523 &selection.have_closure,
524 selection.peer_held.as_deref(),
525 opts,
526 &selection.name_hashes,
527 None,
528 )?;
529 crate::pack_delta::write_pack_with_deltas(odb, &plan.entries, opts, None, w, None)
530}
531
532#[allow(clippy::too_many_arguments)]
541pub fn write_upload_pack_stream(
542 repo: &Repository,
543 wants: &[ObjectId],
544 haves: &[ObjectId],
545 source_shallow: &HashSet<ObjectId>,
546 have_shallow: &HashSet<ObjectId>,
547 filter: Option<&ObjectFilter>,
548 opts: &PackBuildOptions,
549 progress: &mut dyn FnMut(PackProgress),
550 w: &mut dyn Write,
551) -> Result<PackStats> {
552 repo.odb().with_pack_read_context(|| {
553 write_upload_pack_stream_in_context(
554 repo,
555 wants,
556 haves,
557 source_shallow,
558 have_shallow,
559 filter,
560 opts,
561 progress,
562 w,
563 )
564 })
565}
566
567#[allow(clippy::too_many_arguments)]
568fn write_upload_pack_stream_in_context(
569 repo: &Repository,
570 wants: &[ObjectId],
571 haves: &[ObjectId],
572 source_shallow: &HashSet<ObjectId>,
573 have_shallow: &HashSet<ObjectId>,
574 filter: Option<&ObjectFilter>,
575 opts: &PackBuildOptions,
576 progress: &mut dyn FnMut(PackProgress),
577 w: &mut dyn Write,
578) -> Result<PackStats> {
579 if filter.is_some() {
580 return write_pack_with_shallow_and_filter(
581 repo.odb(),
582 wants,
583 haves,
584 have_shallow,
585 source_shallow,
586 filter,
587 opts,
588 HaveBoundary::CommitClosure,
589 w,
590 );
591 }
592 if opts.use_bitmaps {
593 if let Some(stats) = crate::pack_reuse::try_write_streaming_pack(
594 repo,
595 wants,
596 haves,
597 source_shallow,
598 have_shallow,
599 filter,
600 opts,
601 progress,
602 w,
603 )? {
604 return Ok(stats);
605 }
606 }
607 let _ = progress;
608 PackObjects::new(repo.odb(), PackObjectsOptions::from(*opts))
609 .wants(wants)
610 .haves(haves)
611 .source_shallow_grafts(source_shallow)
612 .have_shallow_grafts(have_shallow)
613 .write_to(w)
614}
615
616fn write_pack_header(w: &mut dyn Write, count: u32) -> Result<()> {
617 w.write_all(b"PACK").map_err(Error::Io)?;
618 w.write_all(&2u32.to_be_bytes()).map_err(Error::Io)?;
619 w.write_all(&count.to_be_bytes()).map_err(Error::Io)?;
620 Ok(())
621}
622
623pub(crate) fn write_delta_pack_stream(
625 odb: &Odb,
626 plan: &DeltaPlan,
627 opts: &PackBuildOptions,
628 w: &mut dyn Write,
629 reuse: Option<&mut crate::pack_reuse::StreamingReuse>,
630) -> Result<PackStats> {
631 let (pack_bytes_len, reuse_stats) = if let Some(reuse) = reuse {
632 let pack = serialize_delta_plan_stream(odb, plan, opts, Some(reuse))?;
633 let len = pack.bytes.len() as u64;
634 w.write_all(&pack.bytes).map_err(Error::Io)?;
635 (len, reuse.take_stats())
636 } else {
637 let pack = serialize_delta_plan_stream(odb, plan, opts, None)?;
638 let len = pack.bytes.len() as u64;
639 w.write_all(&pack.bytes).map_err(Error::Io)?;
640 (len, crate::pack_reuse::ReuseWriteStats::default())
641 };
642 let delta_count = plan.entries.iter().filter(|e| e.base.is_some()).count() as u32;
643 Ok(PackStats {
644 object_count: u32::try_from(plan.entries.len())
645 .map_err(|_| Error::CorruptObject("pack object count exceeds u32".to_owned()))?,
646 pack_bytes: pack_bytes_len,
647 delta_count,
648 verbatim_reuse_bytes: reuse_stats.verbatim_reuse_bytes,
649 verbatim_reuse_objects: reuse_stats.verbatim_reuse_objects,
650 pack_ofs_fixup_objects: reuse_stats.pack_ofs_fixup_objects,
651 pack_ref_from_ofs_objects: reuse_stats.pack_ref_from_ofs_objects,
652 })
653}
654
655fn write_delta_plan_stream_body(
656 w: &mut dyn Write,
657 odb: &Odb,
658 plan: &DeltaPlan,
659 opts: &PackBuildOptions,
660 reuse: Option<&mut crate::pack_reuse::StreamingReuse>,
661) -> Result<Vec<(ObjectId, u64, u32)>> {
662 let count = u32::try_from(plan.entries.len())
663 .map_err(|_| Error::CorruptObject("pack object count exceeds u32".to_owned()))?;
664 write_pack_header(w, count)?;
665 let mut index_rows = Vec::with_capacity(plan.entries.len());
666 write_planned_entries(w, odb, plan, opts, 12, reuse, &mut index_rows)?;
667 Ok(index_rows)
668}
669
670pub(crate) fn serialize_delta_plan_stream(
672 odb: &Odb,
673 plan: &DeltaPlan,
674 opts: &PackBuildOptions,
675 reuse: Option<&mut crate::pack_reuse::StreamingReuse>,
676) -> Result<SerializedDeltaPack> {
677 let mut buf = Vec::new();
678 let index_rows = write_delta_plan_stream_body(&mut buf, odb, plan, opts, reuse)?;
679 append_pack_trailer(&mut buf, odb.hash_algo());
680 Ok(SerializedDeltaPack {
681 bytes: buf,
682 index_rows,
683 staging_path: None,
684 })
685}
686
687fn write_planned_entries(
688 w: &mut dyn Write,
689 odb: &Odb,
690 plan: &DeltaPlan,
691 opts: &PackBuildOptions,
692 pack_body_start: u64,
693 mut reuse: Option<&mut crate::pack_reuse::StreamingReuse>,
694 index_rows: &mut Vec<(ObjectId, u64, u32)>,
695) -> Result<()> {
696 let algo = odb.hash_algo();
697 let compression = odb.pack_compression()?;
698 let mut oid_to_offset: OidHashMap<u64> = oid_hash_map();
699 let mut body_written = 0u64;
700
701 for entry in &plan.entries {
702 let start = pack_body_start + body_written;
703 let mut chunk = Vec::new();
704 if entry.base.is_none() {
705 if let Some(reuse) = reuse.as_mut() {
706 if let Some(bytes) = reuse.try_emit_whole(&entry.oid, opts)? {
707 chunk = bytes;
708 }
709 }
710 if chunk.is_empty() {
711 write_whole_pack_object_from_odb(&mut chunk, odb, entry.oid, entry.kind, opts)?;
712 }
713 w.write_all(&chunk).map_err(Error::Io)?;
714 index_rows.push((entry.oid, start, crc32fast::hash(&chunk)));
715 body_written += chunk.len() as u64;
716 oid_to_offset.insert(entry.oid, start);
717 continue;
718 }
719 let Some(base_oid) = entry.base else {
720 continue;
721 };
722 let in_pack_offset = oid_to_offset.get(&base_oid).copied();
723 if let Some((delta_size, pack_bytes, zlib_start, zlib_end)) = &entry.reused_delta_zlib {
724 let delta_len = usize::try_from(*delta_size)
725 .map_err(|_| Error::CorruptObject("reused delta size exceeds usize".to_owned()))?;
726 if let Some(base_off) = in_pack_offset.filter(|_| opts.use_ofs_delta) {
727 let dist = start.checked_sub(base_off).ok_or_else(|| {
728 Error::CorruptObject("ofs-delta distance underflow".to_owned())
729 })?;
730 encode_pack_object_header(&mut chunk, 6, delta_len);
731 encode_ofs_delta_distance(&mut chunk, dist);
732 if let Some(reuse) = reuse.as_mut() {
733 reuse.record_ofs_fixup();
734 }
735 } else {
736 encode_pack_object_header(&mut chunk, 7, delta_len);
737 chunk.extend_from_slice(base_oid.as_bytes());
738 }
739 chunk.extend_from_slice(&pack_bytes[*zlib_start..*zlib_end]);
740 if let Some(reuse) = reuse.as_mut() {
741 reuse.record_verbatim_zlib((zlib_end - zlib_start) as u64);
742 }
743 } else {
744 let computed;
745 let (delta_len, zlib): (usize, &[u8]) = match &entry.searched_delta {
746 Some((len, zlib)) => (*len, zlib),
747 None => {
748 let target_data = odb.read(&entry.oid)?.data;
749 let base_data = odb.read(&base_oid)?.data;
750 let delta = if target_data.starts_with(&base_data)
751 && target_data.len() > base_data.len()
752 {
753 encode_prefix_extension_delta(&base_data, &target_data)?
754 } else {
755 DeltaIndex::new(&base_data)
756 .encode(&target_data, 0)
757 .ok_or_else(|| {
758 Error::CorruptObject(
759 "failed to encode pack delta for object".into(),
760 )
761 })?
762 };
763 let mut buf = Vec::new();
764 write_zlib(&mut buf, &delta, compression)?;
765 computed = buf;
766 (delta.len(), &computed)
767 }
768 };
769 if let Some(base_off) = in_pack_offset.filter(|_| opts.use_ofs_delta) {
770 let dist = start.checked_sub(base_off).ok_or_else(|| {
771 Error::CorruptObject("ofs-delta distance underflow".to_owned())
772 })?;
773 encode_pack_object_header(&mut chunk, 6, delta_len);
774 encode_ofs_delta_distance(&mut chunk, dist);
775 } else {
776 encode_pack_object_header(&mut chunk, 7, delta_len);
777 if base_oid.as_bytes().len() != algo.len() {
778 return Err(Error::CorruptObject(
779 "ref-delta base oid width mismatch".to_owned(),
780 ));
781 }
782 chunk.extend_from_slice(base_oid.as_bytes());
783 }
784 chunk.extend_from_slice(zlib);
785 }
786 w.write_all(&chunk).map_err(Error::Io)?;
787 index_rows.push((entry.oid, start, crc32fast::hash(&chunk)));
788 body_written += chunk.len() as u64;
789 oid_to_offset.insert(entry.oid, start);
790 }
791 Ok(())
792}
793
794pub fn build_pack_from_send_list(
800 odb: &Odb,
801 send: &[ObjectId],
802 have_closure: &HashSet<ObjectId>,
803 opts: &PackBuildOptions,
804 blob_name_hashes: &HashMap<ObjectId, u32>,
805) -> Result<Vec<u8>> {
806 build_pack_from_send_list_with_parallel_threads(
807 odb,
808 send,
809 have_closure,
810 opts,
811 blob_name_hashes,
812 None,
813 None,
814 )
815}
816
817pub fn build_pack_with_write_threads(
821 odb: &Odb,
822 wants: &[ObjectId],
823 haves: &[ObjectId],
824 opts: &PackBuildOptions,
825 write_threads: Option<usize>,
826) -> Result<Vec<u8>> {
827 build_pack_with_write_threads_full(odb, wants, haves, opts, write_threads)
828 .map(|pack| pack.bytes)
829}
830
831pub(crate) fn build_pack_with_write_threads_full(
833 odb: &Odb,
834 wants: &[ObjectId],
835 haves: &[ObjectId],
836 opts: &PackBuildOptions,
837 write_threads: Option<usize>,
838) -> Result<SerializedDeltaPack> {
839 let empty_shallow = empty_object_id_hash_set();
840 let have_closure = reachable_closure(odb, haves, empty_shallow, true, empty_shallow)?;
841 let selection = crate::pack_object_select::enumerate_pack_objects_naming(
842 odb,
843 wants,
844 haves,
845 &crate::pack_object_select::PackEnumerateOptions {
846 filter: None,
847 shallow_grafts: empty_shallow,
848 have_shallow_grafts: Some(empty_shallow),
849 use_bitmaps: opts.use_bitmaps,
850 exclude_objects: Some(&have_closure),
851 peer_odb: None,
852 have_boundary: HaveBoundary::CommitClosure,
853 },
854 NameTrees::for_delta_search(opts),
855 )?;
856 build_pack_from_send_list_full(
857 odb,
858 &selection.send,
859 &selection.have_closure,
860 opts,
861 &selection.name_hashes,
862 None,
863 write_threads,
864 )
865}
866
867pub fn build_pack_with_parallel_threads(
869 odb: &Odb,
870 wants: &[ObjectId],
871 haves: &[ObjectId],
872 opts: &PackBuildOptions,
873 search_threads: Option<usize>,
874 write_threads: Option<usize>,
875) -> Result<Vec<u8>> {
876 let empty_shallow = empty_object_id_hash_set();
877 let have_closure = reachable_closure(odb, haves, empty_shallow, true, empty_shallow)?;
878 let selection = crate::pack_object_select::enumerate_pack_objects_naming(
879 odb,
880 wants,
881 haves,
882 &crate::pack_object_select::PackEnumerateOptions {
883 filter: None,
884 shallow_grafts: empty_shallow,
885 have_shallow_grafts: Some(empty_shallow),
886 use_bitmaps: opts.use_bitmaps,
887 exclude_objects: Some(&have_closure),
888 peer_odb: None,
889 have_boundary: HaveBoundary::CommitClosure,
890 },
891 NameTrees::for_delta_search(opts),
892 )?;
893 build_pack_from_send_list_with_parallel_threads(
894 odb,
895 &selection.send,
896 &selection.have_closure,
897 opts,
898 &selection.name_hashes,
899 search_threads,
900 write_threads,
901 )
902}
903
904pub fn build_pack_from_send_list_with_write_threads(
906 odb: &Odb,
907 send: &[ObjectId],
908 have_closure: &HashSet<ObjectId>,
909 opts: &PackBuildOptions,
910 blob_name_hashes: &HashMap<ObjectId, u32>,
911 write_threads: Option<usize>,
912) -> Result<Vec<u8>> {
913 build_pack_from_send_list_with_parallel_threads(
914 odb,
915 send,
916 have_closure,
917 opts,
918 blob_name_hashes,
919 None,
920 write_threads,
921 )
922}
923
924pub fn build_pack_from_send_list_with_parallel_threads(
926 odb: &Odb,
927 send: &[ObjectId],
928 have_closure: &HashSet<ObjectId>,
929 opts: &PackBuildOptions,
930 blob_name_hashes: &HashMap<ObjectId, u32>,
931 search_threads: Option<usize>,
932 write_threads: Option<usize>,
933) -> Result<Vec<u8>> {
934 build_pack_from_send_list_full(
935 odb,
936 send,
937 have_closure,
938 opts,
939 blob_name_hashes,
940 search_threads,
941 write_threads,
942 )
943 .map(|pack| pack.bytes)
944}
945
946pub(crate) fn build_pack_from_send_list_full(
947 odb: &Odb,
948 send: &[ObjectId],
949 have_closure: &HashSet<ObjectId>,
950 opts: &PackBuildOptions,
951 blob_name_hashes: &HashMap<ObjectId, u32>,
952 search_threads: Option<usize>,
953 write_threads: Option<usize>,
954) -> Result<SerializedDeltaPack> {
955 if !opts.delta {
956 let bytes = serialize_pack(odb, send, opts)?;
957 return Ok(SerializedDeltaPack {
958 bytes,
959 index_rows: Vec::new(),
960 staging_path: None,
961 });
962 }
963 let plan = plan_deltas(
964 odb,
965 send,
966 have_closure,
967 None,
968 opts,
969 blob_name_hashes,
970 search_threads,
971 )?;
972 crate::pack_delta::serialize_pack_with_deltas(odb, &plan.entries, opts, write_threads)
973}
974
975pub(crate) fn build_pack_for_local_fetch(
977 source_odb: &Odb,
978 wants: &[ObjectId],
979 have_odb: &Odb,
980 haves: &[ObjectId],
981 source_shallow: &HashSet<ObjectId>,
982 have_shallow: &HashSet<ObjectId>,
983 opts: &PackBuildOptions,
984) -> Result<Vec<u8>> {
985 let mut buf = Vec::new();
986 PackObjects::new(have_odb, PackObjectsOptions::from(*opts))
987 .with_source_odb(source_odb)
988 .wants(wants)
989 .haves(haves)
990 .have_shallow_grafts(have_shallow)
991 .source_shallow_grafts(source_shallow)
992 .write_to(&mut buf)?;
993 Ok(buf)
994}
995
996fn collect_reachable_excluding(
997 odb: &Odb,
998 roots: &[ObjectId],
999 exclude: &OidHashSet,
1000 skip_missing: bool,
1001 shallow_grafts: &OidHashSet,
1002) -> Result<Vec<ObjectId>> {
1003 let mut visited: OidHashSet = oid_hash_set();
1004 let mut ordered: Vec<ObjectId> = Vec::new();
1005 let mut queue: VecDeque<ObjectId> = VecDeque::new();
1006
1007 let enqueue = |oid: ObjectId,
1008 queue: &mut VecDeque<ObjectId>,
1009 visited: &mut OidHashSet,
1010 ordered: &mut Vec<ObjectId>|
1011 -> bool {
1012 if !visited.insert(oid) {
1013 return false;
1014 }
1015 if !exclude.contains(&oid) {
1016 ordered.push(oid);
1017 }
1018 queue.push_back(oid);
1019 true
1020 };
1021
1022 for &root in roots {
1023 enqueue(root, &mut queue, &mut visited, &mut ordered);
1024 }
1025
1026 let oid_len = odb.hash_algo().len();
1027
1028 while let Some(oid) = queue.pop_front() {
1029 let obj = match odb.read(&oid) {
1030 Ok(o) => o,
1031 Err(_) if skip_missing => continue,
1035 Err(e) => return Err(e),
1036 };
1037 match obj.kind {
1038 ObjectKind::Commit => {
1039 let commit = parse_commit(&obj.data)?;
1040 if !shallow_grafts.contains(&oid) {
1041 for parent in commit.parents {
1042 enqueue(parent, &mut queue, &mut visited, &mut ordered);
1043 }
1044 }
1045 enqueue(commit.tree, &mut queue, &mut visited, &mut ordered);
1046 }
1047 ObjectKind::Tree => {
1048 for entry in TreeRefIter::with_oid_len(&obj.data, oid_len) {
1049 let entry = entry?;
1050 if entry.mode == 0o160000 {
1053 continue;
1054 }
1055 enqueue(entry.oid, &mut queue, &mut visited, &mut ordered);
1056 }
1057 }
1058 ObjectKind::Tag => {
1059 let tag = parse_tag(&obj.data)?;
1060 enqueue(tag.object, &mut queue, &mut visited, &mut ordered);
1061 }
1062 ObjectKind::Blob => {}
1063 }
1064 }
1065
1066 Ok(ordered)
1067}
1068
1069pub(crate) fn pack_type_code(kind: ObjectKind) -> u8 {
1071 match kind {
1072 ObjectKind::Commit => 1,
1073 ObjectKind::Tree => 2,
1074 ObjectKind::Blob => 3,
1075 ObjectKind::Tag => 4,
1076 }
1077}
1078
1079pub(crate) fn encode_pack_object_header(buf: &mut Vec<u8>, type_code: u8, payload_len: usize) {
1082 let mut size = payload_len;
1083 let first = ((type_code & 0x7) << 4) | (size & 0x0f) as u8;
1084 size >>= 4;
1085 if size > 0 {
1086 buf.push(first | 0x80);
1087 while size > 0 {
1088 let b = (size & 0x7f) as u8;
1089 size >>= 7;
1090 buf.push(if size > 0 { b | 0x80 } else { b });
1091 }
1092 } else {
1093 buf.push(first);
1094 }
1095}
1096
1097fn serialize_pack(odb: &Odb, oids: &[ObjectId], opts: &PackBuildOptions) -> Result<Vec<u8>> {
1100 let mut buf = Vec::new();
1101 buf.extend_from_slice(b"PACK");
1102 buf.extend_from_slice(&2u32.to_be_bytes());
1103 let count = u32::try_from(oids.len())
1104 .map_err(|_| Error::CorruptObject("pack object count exceeds u32".to_owned()))?;
1105 buf.extend_from_slice(&count.to_be_bytes());
1106
1107 for oid in oids {
1108 let kind = odb.read_info(oid)?.kind;
1109 write_whole_pack_object_from_odb(&mut buf, odb, *oid, kind, opts)?;
1110 }
1111
1112 append_pack_trailer(&mut buf, odb.hash_algo());
1113 Ok(buf)
1114}
1115
1116fn write_whole_pack_object_from_odb(
1118 buf: &mut Vec<u8>,
1119 odb: &Odb,
1120 oid: ObjectId,
1121 kind: ObjectKind,
1122 opts: &PackBuildOptions,
1123) -> Result<()> {
1124 if opts.reuse_objects && odb.hash_algo() == HashAlgo::Sha1 {
1125 if let Some(raw) = crate::pack::packed_full_object_slice(odb.objects_dir(), &oid)? {
1126 buf.extend_from_slice(&raw);
1127 return Ok(());
1128 }
1129 }
1130 let size = odb.read_info(&oid)?.size;
1131 let stream_from = odb.big_file_threshold()?;
1132 if kind == ObjectKind::Blob && size >= stream_from {
1133 return odb
1134 .with_stream(&oid, |mut stream| {
1135 let payload_len = usize::try_from(stream.size).map_err(|_| {
1136 Error::CorruptObject(format!("object size overflow for {}", oid.to_hex()))
1137 })?;
1138 encode_pack_object_header(buf, pack_type_code(kind), payload_len);
1139 write_zlib_from_reader(buf, &mut stream.reader, odb.pack_compression()?)
1140 })?
1141 .ok_or_else(|| Error::ObjectNotFound(oid.to_hex()));
1142 }
1143 let obj = odb.read(&oid)?;
1144 write_whole_pack_object(buf, odb, oid, kind, &obj.data, opts)
1145}
1146
1147fn write_whole_pack_object(
1149 buf: &mut Vec<u8>,
1150 odb: &Odb,
1151 oid: ObjectId,
1152 kind: ObjectKind,
1153 data: &[u8],
1154 opts: &PackBuildOptions,
1155) -> Result<()> {
1156 if opts.reuse_objects && odb.hash_algo() == HashAlgo::Sha1 {
1157 if let Some(raw) = crate::pack::packed_full_object_slice(odb.objects_dir(), &oid)? {
1158 buf.extend_from_slice(&raw);
1159 return Ok(());
1160 }
1161 }
1162 encode_pack_object_header(buf, pack_type_code(kind), data.len());
1163 let compression = odb.pack_compression()?;
1164 write_zlib(buf, data, compression)
1165}
1166
1167pub(crate) fn append_pack_trailer(buf: &mut Vec<u8>, algo: HashAlgo) {
1170 buf.extend_from_slice(&algo.checksum_digest(&*buf));
1171}
1172
1173pub(crate) fn append_whole_object_from_odb(
1177 buf: &mut Vec<u8>,
1178 odb: &Odb,
1179 oid: ObjectId,
1180) -> Result<ObjectKind> {
1181 let obj = odb.read(&oid)?;
1182 write_whole_pack_object(
1183 buf,
1184 odb,
1185 oid,
1186 obj.kind,
1187 &obj.data,
1188 &PackBuildOptions::default(),
1189 )?;
1190 Ok(obj.kind)
1191}
1192
1193pub(crate) struct PlannedEntry {
1195 pub(crate) oid: ObjectId,
1196 pub(crate) kind: ObjectKind,
1197 pub(crate) base: Option<ObjectId>,
1200 pub(crate) searched_delta: Option<(usize, Vec<u8>)>,
1204 pub(crate) reused_delta_zlib:
1207 Option<(u64, std::sync::Arc<crate::pack::PackData>, usize, usize)>,
1208}
1209
1210pub(crate) struct DeltaPlan {
1213 pub(crate) entries: Vec<PlannedEntry>,
1214}
1215
1216pub(crate) struct SerializedDeltaPack {
1218 pub bytes: Vec<u8>,
1219 pub index_rows: Vec<(ObjectId, u64, u32)>,
1220 pub staging_path: Option<std::path::PathBuf>,
1222}
1223
1224fn read_object_infos(
1226 odb: &Odb,
1227 oids: &[ObjectId],
1228 search_threads: Option<usize>,
1229) -> Result<Vec<ObjectInfo>> {
1230 let par = crate::pack_delta::pack_delta_search_parallel_options(odb, search_threads)?;
1231 let read = |oid: &ObjectId| odb.read_info(oid);
1232 if par.threads.get() <= 1 || oids.len() < 32 {
1233 return oids.iter().map(read).collect();
1234 }
1235 try_par_map_force(oids, par, read).map_err(|e| match e {
1236 ParallelHashError::Task(err) => err,
1237 })
1238}
1239
1240fn order_bases_first(entries: &mut [PlannedEntry]) {
1246 let n = entries.len();
1247 let bases: Vec<Option<usize>> = {
1248 let position: OidHashMap<usize> = entries
1249 .iter()
1250 .enumerate()
1251 .map(|(i, e)| (e.oid, i))
1252 .collect();
1253 entries
1254 .iter()
1255 .map(|e| e.base.and_then(|b| position.get(&b).copied()))
1256 .collect()
1257 };
1258 let mut dest = vec![usize::MAX; n];
1260 let mut next = 0usize;
1261 let mut chain = Vec::new();
1262 for i in 0..n {
1263 let mut cur = Some(i);
1264 while let Some(j) = cur {
1265 if dest[j] != usize::MAX || chain.len() > n {
1266 break;
1267 }
1268 chain.push(j);
1269 cur = bases[j];
1270 }
1271 while let Some(j) = chain.pop() {
1272 if dest[j] == usize::MAX {
1273 dest[j] = next;
1274 next += 1;
1275 }
1276 }
1277 }
1278 for i in 0..n {
1279 while dest[i] != i {
1280 let d = dest[i];
1281 entries.swap(i, d);
1282 dest.swap(i, d);
1283 }
1284 }
1285}
1286
1287pub(crate) fn objects_dir_has_local_packs(objects_dir: &Path) -> bool {
1289 let pack_dir = objects_dir.join("pack");
1290 if !pack_dir.is_dir() {
1291 return false;
1292 }
1293 std::fs::read_dir(&pack_dir)
1294 .ok()
1295 .into_iter()
1296 .flatten()
1297 .filter_map(|e| e.ok())
1298 .any(|e| e.path().extension().is_some_and(|ext| ext == "pack"))
1299}
1300
1301pub(crate) fn plan_deltas(
1328 odb: &Odb,
1329 send: &[ObjectId],
1330 have_tips: &HashSet<ObjectId>,
1331 peer_held: Option<&ReachableSet>,
1332 opts: &PackBuildOptions,
1333 name_hashes: &HashMap<ObjectId, u32>,
1334 search_threads: Option<usize>,
1335) -> Result<DeltaPlan> {
1336 let has_local_packs = objects_dir_has_local_packs(odb.objects_dir());
1337 let reuse_on_disk = opts.reuse_deltas && odb.hash_algo() == HashAlgo::Sha1 && has_local_packs;
1338 let search = opts.window > 0 && opts.max_depth > 0;
1339 let thin = opts.thin && search;
1340 let infos = read_object_infos(odb, send, search_threads)?;
1341 let in_pack: HashSet<ObjectId> = if reuse_on_disk || thin || opts.respect_islands {
1342 send.iter().copied().collect()
1343 } else {
1344 HashSet::new()
1345 };
1346
1347 let islands = load_islands_for_pack(odb, &in_pack, opts);
1351
1352 let mut delta_to_base: OidHashMap<ObjectId> = oid_hash_map();
1354 let mut reused_zlib: HashMap<
1356 ObjectId,
1357 (u64, std::sync::Arc<crate::pack::PackData>, usize, usize),
1358 > = HashMap::new();
1359
1360 if reuse_on_disk {
1362 let mut reusable_bases = in_pack.clone();
1365 if opts.thin && !have_tips.is_empty() {
1366 for &tip in have_tips {
1367 if odb.read_info(&tip)?.kind == ObjectKind::Commit {
1368 reusable_bases.extend(blob_oids_in_commit(odb, tip)?);
1369 }
1370 }
1371 }
1372 let reusable_bases = Cow::Owned(reusable_bases);
1373 let empty_resolved: HashMap<ObjectId, Object> = HashMap::new();
1374 let candidates = crate::pack::collect_packed_delta_reuse_for_send(
1375 odb.objects_dir(),
1376 &in_pack,
1377 &reusable_bases,
1378 &empty_resolved,
1379 )?;
1380 for (t, (base, size, pack_bytes, zlib_start, zlib_end)) in candidates {
1381 if base != t && reusable_bases.contains(&base) && islands.in_same_island(&t, &base) {
1382 delta_to_base.insert(t, base);
1383 reused_zlib.insert(t, (size, pack_bytes, zlib_start, zlib_end));
1384 }
1385 }
1386 for (oid, info) in send.iter().zip(&infos) {
1388 if info.kind == ObjectKind::Blob && info.size == 0 && reused_zlib.remove(oid).is_some()
1389 {
1390 delta_to_base.remove(oid);
1391 }
1392 }
1393 }
1394
1395 let reuse_delta_bases: OidHashSet = delta_to_base
1399 .iter()
1400 .filter(|(t, _)| reused_zlib.contains_key(t))
1401 .map(|(_, b)| *b)
1402 .collect();
1403
1404 let mut searched: OidHashMap<(usize, Vec<u8>)> = oid_hash_map();
1405 if search {
1406 let found = search_new_deltas(
1407 odb,
1408 send,
1409 &infos,
1410 |oid| delta_to_base.contains_key(oid) || reuse_delta_bases.contains(oid),
1411 name_hashes,
1412 &islands,
1413 opts,
1414 search_threads,
1415 has_local_packs,
1416 )?;
1417 for (target, base, delta) in found {
1418 delta_to_base.insert(target, base);
1419 searched.insert(target, delta);
1420 }
1421 }
1422
1423 if thin {
1424 let hash_len = odb.hash_algo().len();
1425 let thin_targets: Vec<ObjectId> = send
1426 .iter()
1427 .zip(&infos)
1428 .filter(|(t, info)| {
1429 info.kind == ObjectKind::Blob
1430 && (info.size as usize)
1431 .saturating_div(2)
1432 .saturating_sub(hash_len)
1433 > 0
1434 && !delta_to_base.contains_key(*t)
1435 && !reuse_delta_bases.contains(*t)
1436 })
1437 .map(|(t, _)| *t)
1438 .collect();
1439 let in_pack_oid: OidHashSet = in_pack.iter().copied().collect();
1440 let window = opts.window.max(opts.max_depth).max(1);
1441 let bitmap_index = peer_held.map(ReachableSet::bitmap_index);
1442 for t in thin_targets {
1443 let t_data = odb.read(&t)?.data;
1444 let path_key = name_hashes.get(&t).copied().unwrap_or(0);
1445 let Some((base, _base_data)) = find_thin_pack_peer_blob_base(
1446 odb,
1447 &t,
1448 &t_data,
1449 path_key,
1450 &in_pack_oid,
1451 have_tips,
1452 peer_held,
1453 bitmap_index,
1454 &islands,
1455 window,
1456 hash_len,
1457 )?
1458 else {
1459 continue;
1460 };
1461 delta_to_base.insert(t, base);
1462 }
1463 }
1464 drop(in_pack);
1465
1466 if opts.max_depth > 0 && !delta_to_base.is_empty() {
1467 let mut delta_std: HashMap<ObjectId, ObjectId> = delta_to_base.drain().collect();
1469 crate::pack::apply_delta_depth_limit(&mut delta_std, opts.max_depth);
1470 delta_to_base.extend(delta_std);
1471 }
1472
1473 let mut entries: Vec<PlannedEntry> = send
1477 .iter()
1478 .zip(&infos)
1479 .map(|(&oid, info)| {
1480 let base = delta_to_base.remove(&oid);
1481 let (searched_delta, reused_delta_zlib) = match base {
1482 Some(_) => (searched.remove(&oid), reused_zlib.remove(&oid)),
1483 None => (None, None),
1484 };
1485 PlannedEntry {
1486 oid,
1487 kind: info.kind,
1488 base,
1489 searched_delta,
1490 reused_delta_zlib,
1491 }
1492 })
1493 .collect();
1494 drop((infos, delta_to_base, searched, reused_zlib));
1495 order_bases_first(&mut entries);
1496 Ok(DeltaPlan { entries })
1497}
1498
1499type SearchedDelta = (ObjectId, ObjectId, (usize, Vec<u8>));
1501
1502#[allow(clippy::too_many_arguments)]
1505fn search_new_deltas(
1506 odb: &Odb,
1507 send: &[ObjectId],
1508 infos: &[ObjectInfo],
1509 skip: impl Fn(&ObjectId) -> bool,
1510 name_hashes: &HashMap<ObjectId, u32>,
1511 islands: &crate::delta_islands::DeltaIslands,
1512 opts: &PackBuildOptions,
1513 search_threads: Option<usize>,
1514 has_local_packs: bool,
1515) -> Result<Vec<SearchedDelta>> {
1516 u32::try_from(send.len())
1517 .map_err(|_| Error::CorruptObject("too many objects for one pack".to_owned()))?;
1518 let ids: Vec<u32> = (0..send.len())
1519 .filter(|&i| infos[i].size > 0 && !skip(&send[i]))
1520 .map(|i| i as u32)
1521 .collect();
1522 let packed = if opts.reuse_deltas && has_local_packs {
1524 let oids: OidHashSet = ids.iter().map(|&i| send[i as usize]).collect();
1525 crate::pack::locate_packed_objects(odb.objects_dir(), &oids).ok()
1526 } else {
1527 None
1528 };
1529 let candidates: Vec<crate::delta_search::DeltaCandidate> = ids
1530 .into_iter()
1531 .map(|id| {
1532 let oid = send[id as usize];
1533 let info = infos[id as usize];
1534 let slot = packed.as_ref().and_then(|p| p.get(&oid));
1535 crate::delta_search::DeltaCandidate {
1536 id,
1537 oid,
1538 kind: info.kind,
1539 size: info.size,
1540 name_hash: name_hashes.get(&oid).copied().unwrap_or(0),
1541 whole_in_pack: slot.filter(|s| s.whole).map(|s| s.pack),
1542 in_pack: slot.map(|s| s.pack),
1543 dependent_depth: 0,
1544 }
1545 })
1546 .collect();
1547 drop(packed);
1548 let window_memory = crate::pack_delta::pack_config(odb)?.pack_window_memory_bytes();
1549 let threads = crate::pack_delta::pack_delta_search_parallel_options(odb, search_threads)?;
1550 let in_islands = |target: &ObjectId, base: &ObjectId| islands.in_same_island(target, base);
1551 let found = crate::delta_search::search_deltas(
1552 odb,
1553 candidates,
1554 crate::delta_search::DeltaSearchParams {
1555 window: opts.window,
1556 max_depth: opts.max_depth,
1557 window_memory,
1558 hash_len: odb.hash_algo().len(),
1559 compression: odb.pack_compression()?,
1560 threads: threads.threads.get(),
1561 },
1562 &in_islands,
1563 )?;
1564 Ok(found
1565 .into_iter()
1566 .map(|d| {
1567 (
1568 send[d.target as usize],
1569 send[d.base as usize],
1570 (d.delta_len, d.zlib),
1571 )
1572 })
1573 .collect())
1574}
1575
1576fn peer_side_delta_base(
1577 base: &ObjectId,
1578 in_send: &OidHashSet,
1579 have_tips: &HashSet<ObjectId>,
1580 peer_held: Option<&ReachableSet>,
1581) -> bool {
1582 if in_send.contains(base) {
1583 return false;
1584 }
1585 peer_held.is_some_and(|held| held.contains(base)) || have_tips.contains(base)
1586}
1587
1588#[allow(clippy::too_many_arguments)]
1589fn record_thin_peer_candidate(
1590 best: &mut Option<(ObjectId, usize, Vec<u8>)>,
1591 target: &ObjectId,
1592 candidate: ObjectId,
1593 base_data: Vec<u8>,
1594 target_data: &[u8],
1595 in_send: &OidHashSet,
1596 have_tips: &HashSet<ObjectId>,
1597 peer_held: Option<&ReachableSet>,
1598 islands: &DeltaIslands,
1599 max_size: usize,
1600 known_peer: bool,
1601) {
1602 if !known_peer && !peer_side_delta_base(&candidate, in_send, have_tips, peer_held) {
1603 return;
1604 }
1605 if !islands.in_same_island(target, &candidate) {
1606 return;
1607 }
1608 let sizediff = if base_data.len() < target_data.len() {
1609 target_data.len() - base_data.len()
1610 } else {
1611 0
1612 };
1613 if sizediff >= max_size || target_data.len() < base_data.len() / 32 {
1614 return;
1615 }
1616 let Some(ds) = thin_pack_delta_size(&base_data, target_data, max_size) else {
1617 return;
1618 };
1619 let better = best.as_ref().is_none_or(|(_, prev, _)| ds < *prev);
1620 if better {
1621 *best = Some((candidate, ds, base_data));
1622 }
1623}
1624
1625fn thin_pack_delta_size(base_data: &[u8], target_data: &[u8], max_size: usize) -> Option<usize> {
1626 if target_data.starts_with(base_data) && target_data.len() > base_data.len() {
1627 encode_prefix_extension_delta(base_data, target_data)
1628 .ok()
1629 .map(|d| d.len())
1630 } else {
1631 DeltaIndex::new(base_data)
1632 .encode(target_data, max_size)
1633 .map(|d| d.len())
1634 }
1635}
1636
1637#[allow(clippy::too_many_arguments)]
1638fn find_thin_pack_peer_blob_base(
1639 odb: &Odb,
1640 target: &ObjectId,
1641 target_data: &[u8],
1642 name_hash: u32,
1643 in_send: &OidHashSet,
1644 have_tips: &HashSet<ObjectId>,
1645 peer_held: Option<&ReachableSet>,
1646 bitmap_index: Option<&std::sync::Arc<BitmapIndex>>,
1647 islands: &DeltaIslands,
1648 window: usize,
1649 hash_len: usize,
1650) -> Result<Option<(ObjectId, Vec<u8>)>> {
1651 let max_size = target_data.len().saturating_div(2).saturating_sub(hash_len);
1652 if max_size == 0 {
1653 return Ok(None);
1654 }
1655
1656 let mut best: Option<(ObjectId, usize, Vec<u8>)> = None;
1657 let mut tried = 0usize;
1658
1659 if let (Some(held), Some(index)) = (peer_held, bitmap_index) {
1660 let peer_blobs: Vec<ObjectId> = held.iter_by_kind(ObjectKind::Blob).collect();
1661 for oid in peer_blobs.into_iter().rev() {
1662 if tried >= window {
1663 break;
1664 }
1665 let Some(pos) = index.position_of(&oid) else {
1666 continue;
1667 };
1668 let Some(h) = index.name_hash(pos) else {
1669 continue;
1670 };
1671 if h != name_hash {
1672 continue;
1673 }
1674 if in_send.contains(&oid) {
1675 continue;
1676 }
1677 tried += 1;
1678 let obj = odb.read(&oid)?;
1679 if obj.kind != ObjectKind::Blob || obj.data.is_empty() {
1680 continue;
1681 }
1682 record_thin_peer_candidate(
1683 &mut best,
1684 target,
1685 oid,
1686 obj.data,
1687 target_data,
1688 in_send,
1689 have_tips,
1690 peer_held,
1691 islands,
1692 max_size,
1693 false,
1694 );
1695 }
1696 }
1697 if best.is_none() {
1698 let opened = open_pack_bitmap_index(odb);
1699 let index = bitmap_index.or(opened.as_ref());
1700 if let Some(index) = index {
1701 let count = index.object_count();
1702 let mut seen_same_path = 0usize;
1703 for pos in (0..count).rev() {
1704 if seen_same_path >= window {
1705 break;
1706 }
1707 let Some(h) = index.name_hash(pos) else {
1708 continue;
1709 };
1710 if h != name_hash {
1711 continue;
1712 }
1713 let Ok(oid) = index.oid_at(pos) else {
1714 continue;
1715 };
1716 if in_send.contains(&oid) {
1717 continue;
1718 }
1719 seen_same_path += 1;
1720 let info = match odb.read_info(&oid) {
1721 Ok(info) => info,
1722 Err(_) => continue,
1723 };
1724 if info.kind != ObjectKind::Blob || info.size == 0 {
1725 continue;
1726 }
1727 if !peer_side_delta_base(&oid, in_send, have_tips, peer_held) {
1728 continue;
1729 }
1730 let obj = odb.read(&oid)?;
1731 record_thin_peer_candidate(
1732 &mut best,
1733 target,
1734 oid,
1735 obj.data,
1736 target_data,
1737 in_send,
1738 have_tips,
1739 peer_held,
1740 islands,
1741 max_size,
1742 false,
1743 );
1744 }
1745 }
1746 }
1747 if best.is_none() {
1748 for (oid, data) in peer_blobs_from_have_commits(odb, have_tips, name_hash)? {
1749 record_thin_peer_candidate(
1750 &mut best,
1751 target,
1752 oid,
1753 data,
1754 target_data,
1755 in_send,
1756 have_tips,
1757 peer_held,
1758 islands,
1759 max_size,
1760 true,
1761 );
1762 }
1763 }
1764
1765 Ok(best.map(|(oid, _, data)| (oid, data)))
1766}
1767
1768fn blob_oids_in_commit(odb: &Odb, commit_oid: ObjectId) -> Result<HashSet<ObjectId>> {
1769 let mut out = HashSet::new();
1770 let commit_obj = odb.read(&commit_oid)?;
1771 if commit_obj.kind != ObjectKind::Commit {
1772 return Ok(out);
1773 }
1774 let commit = parse_commit(&commit_obj.data)?;
1775 let oid_len = odb.hash_algo().len();
1776 let mut queue = VecDeque::from([commit.tree]);
1777 while let Some(tree_oid) = queue.pop_front() {
1778 let tree_obj = odb.read(&tree_oid)?;
1779 for entry in TreeRefIter::with_oid_len(&tree_obj.data, oid_len) {
1780 let entry = entry?;
1781 if entry.mode == 0o160000 {
1782 continue;
1783 }
1784 if entry.mode & 0o170000 == 0o040000 {
1785 queue.push_back(entry.oid);
1786 } else {
1787 out.insert(entry.oid);
1788 }
1789 }
1790 }
1791 Ok(out)
1792}
1793
1794fn peer_blobs_from_have_commits(
1795 odb: &Odb,
1796 have_tips: &HashSet<ObjectId>,
1797 name_hash: u32,
1798) -> Result<Vec<(ObjectId, Vec<u8>)>> {
1799 let oid_len = odb.hash_algo().len();
1800 let mut out = Vec::new();
1801 for &commit_oid in have_tips {
1802 let commit_obj = match odb.read(&commit_oid) {
1803 Ok(o) => o,
1804 Err(_) => continue,
1805 };
1806 if commit_obj.kind != ObjectKind::Commit {
1807 continue;
1808 }
1809 let commit = parse_commit(&commit_obj.data)?;
1810 let mut queue = VecDeque::from([(commit.tree, String::new())]);
1811 while let Some((tree_oid, path)) = queue.pop_front() {
1812 let tree_obj = match odb.read(&tree_oid) {
1813 Ok(o) => o,
1814 Err(_) => continue,
1815 };
1816 for entry in TreeRefIter::with_oid_len(&tree_obj.data, oid_len) {
1817 let entry = entry?;
1818 if entry.mode == 0o160000 {
1819 continue;
1820 }
1821 let child_path = if path.is_empty() {
1822 String::from_utf8_lossy(entry.name).into_owned()
1823 } else {
1824 format!("{}/{}", path, String::from_utf8_lossy(entry.name))
1825 };
1826 match entry.mode & 0o170000 {
1827 0o040000 => queue.push_back((entry.oid, child_path)),
1828 0o100000 | 0o120000 => {
1829 if pack_name_hash(&child_path) != name_hash {
1830 continue;
1831 }
1832 let blob = odb.read(&entry.oid)?;
1833 if blob.kind == ObjectKind::Blob && !blob.data.is_empty() {
1834 out.push((entry.oid, blob.data));
1835 }
1836 }
1837 _ => queue.push_back((entry.oid, child_path)),
1838 }
1839 }
1840 }
1841 }
1842 Ok(out)
1843}
1844
1845fn open_pack_bitmap_index(odb: &Odb) -> Option<std::sync::Arc<BitmapIndex>> {
1846 let git_dir = odb.config_git_dir()?;
1847 let repo = Repository::open(git_dir, &RepositoryOptions::empty()).ok()?;
1848 BitmapIndex::open(&repo).ok().flatten()
1849}
1850
1851fn load_islands_for_pack(
1857 odb: &Odb,
1858 in_pack: &HashSet<ObjectId>,
1859 opts: &PackBuildOptions,
1860) -> crate::delta_islands::DeltaIslands {
1861 if !opts.respect_islands {
1862 return crate::delta_islands::DeltaIslands::default();
1863 }
1864 let Some(git_dir) = odb.config_git_dir() else {
1865 return crate::delta_islands::DeltaIslands::default();
1866 };
1867 let Ok(repo) = crate::repo::Repository::open(git_dir, &RepositoryOptions::empty()) else {
1868 return crate::delta_islands::DeltaIslands::default();
1869 };
1870 let env = crate::environment::Environment::empty();
1871 let cfg = crate::config::ConfigSet::load(&env, Some(git_dir), true).unwrap_or_default();
1872 crate::delta_islands::load_delta_islands(&repo, &cfg, in_pack)
1873}
1874
1875fn write_zlib(buf: &mut Vec<u8>, data: &[u8], compression: Compression) -> Result<()> {
1877 let mut enc = ZlibEncoder::new(Vec::new(), compression);
1878 enc.write_all(data).map_err(Error::Io)?;
1879 let compressed = enc.finish().map_err(Error::Io)?;
1880 buf.extend_from_slice(&compressed);
1881 Ok(())
1882}
1883
1884fn write_zlib_from_reader(
1886 buf: &mut Vec<u8>,
1887 reader: &mut dyn std::io::Read,
1888 compression: Compression,
1889) -> Result<()> {
1890 let mut enc = ZlibEncoder::new(Vec::new(), compression);
1891 std::io::copy(reader, &mut enc).map_err(Error::Io)?;
1892 let compressed = enc.finish().map_err(Error::Io)?;
1893 buf.extend_from_slice(&compressed);
1894 Ok(())
1895}
1896
1897pub(crate) fn encode_ofs_delta_distance(buf: &mut Vec<u8>, mut ofs: u64) {
1900 let mut dheader = [0u8; 32];
1901 let mut pos = dheader.len() - 1;
1902 dheader[pos] = (ofs & 0x7f) as u8;
1903 while {
1904 ofs >>= 7;
1905 ofs != 0
1906 } {
1907 pos -= 1;
1908 ofs -= 1;
1909 dheader[pos] = 0x80 | ((ofs & 0x7f) as u8);
1910 }
1911 buf.extend_from_slice(&dheader[pos..]);
1912}
1913
1914#[cfg(test)]
1915#[allow(clippy::unwrap_used, clippy::expect_used)]
1916pub(crate) mod tests {
1917 use super::*;
1918 use crate::pack_object_select::{enumerate_pack_objects, PackEnumerateOptions};
1919
1920 fn planned(n: u8, base: Option<u8>) -> PlannedEntry {
1921 let oid = |n: u8| ObjectId::from_bytes(&[n; 20]).unwrap();
1922 PlannedEntry {
1923 oid: oid(n),
1924 kind: ObjectKind::Blob,
1925 base: base.map(oid),
1926 searched_delta: None,
1927 reused_delta_zlib: None,
1928 }
1929 }
1930
1931 fn order_of(mut entries: Vec<PlannedEntry>) -> Vec<u8> {
1932 order_bases_first(&mut entries);
1933 entries.iter().map(|e| e.oid.as_bytes()[0]).collect()
1934 }
1935
1936 #[test]
1937 fn bases_are_ordered_before_their_targets() {
1938 let entries = vec![
1940 planned(1, Some(3)),
1941 planned(2, None),
1942 planned(3, Some(5)),
1943 planned(4, Some(9)),
1944 planned(5, None),
1945 planned(6, Some(2)),
1946 ];
1947 assert_eq!(order_of(entries), [5, 3, 1, 2, 4, 6]);
1948 }
1949
1950 #[test]
1951 fn base_cycles_do_not_hang_or_drop_entries() {
1952 let entries = vec![planned(1, Some(2)), planned(2, Some(1)), planned(3, None)];
1953 let mut order = order_of(entries);
1954 order.sort_unstable();
1955 assert_eq!(order, [1, 2, 3]);
1956 }
1957
1958 pub(crate) fn packed_selection(
1960 commits: usize,
1961 ) -> (
1962 tempfile::TempDir,
1963 Repository,
1964 crate::pack_object_select::PackObjectSelection,
1965 ) {
1966 let tmp = tempfile::tempdir().unwrap();
1967 let path = grit_test_support::git_packed_history(tmp.path(), commits);
1968 let tip = grit_test_support::git(&path, &["rev-parse", "main"]);
1969 let tip = ObjectId::from_hex(tip.trim()).unwrap();
1970 let repo = Repository::open(&path, &RepositoryOptions::empty()).unwrap();
1971 let empty = HashSet::new();
1972 let selection = enumerate_pack_objects(
1973 repo.odb(),
1974 &[tip],
1975 &[],
1976 &PackEnumerateOptions {
1977 filter: None,
1978 shallow_grafts: &empty,
1979 have_shallow_grafts: Some(&empty),
1980 use_bitmaps: false,
1981 exclude_objects: None,
1982 peer_odb: None,
1983 have_boundary: HaveBoundary::CommitClosure,
1984 },
1985 )
1986 .unwrap();
1987 (tmp, repo, selection)
1988 }
1989
1990 #[test]
1991 fn serve_plan_reuses_on_disk_deltas() {
1992 let (_tmp, repo, selection) = packed_selection(80);
1993 let opts = PackBuildOptions::for_serve(None, false, true);
1994 let plan = plan_deltas(
1995 repo.odb(),
1996 &selection.send,
1997 &selection.have_closure,
1998 selection.peer_held.as_deref(),
1999 &opts,
2000 &selection.name_hashes,
2001 None,
2002 )
2003 .unwrap();
2004
2005 let reused = plan
2006 .entries
2007 .iter()
2008 .filter(|e| e.reused_delta_zlib.is_some())
2009 .count();
2010 let trees_and_blobs = plan
2011 .entries
2012 .iter()
2013 .filter(|e| matches!(e.kind, ObjectKind::Tree | ObjectKind::Blob))
2014 .count();
2015 assert!(
2016 reused * 2 > trees_and_blobs,
2017 "most trees and blobs keep their on-disk delta ({reused} of {trees_and_blobs})"
2018 );
2019 }
2020
2021 fn loose_history(commits: usize) -> (tempfile::TempDir, Repository, ObjectId) {
2024 let tmp = tempfile::tempdir().unwrap();
2025 let dir = tmp.path();
2026 grit_test_support::git(dir, &["init", "-q", "-b", "main"]);
2027 std::fs::create_dir_all(dir.join("src")).unwrap();
2028 for i in 0..20 {
2029 std::fs::write(
2030 dir.join(format!("src/stable{i}.txt")),
2031 format!("stable {i}\n"),
2032 )
2033 .unwrap();
2034 }
2035 let mut lines: Vec<String> = (0..200).map(|i| format!("line {i} of the file")).collect();
2036 for c in 0..commits {
2037 lines[(c * 13) % 200] = format!("changed in commit {c}");
2038 std::fs::write(dir.join("src/file.txt"), lines.join("\n")).unwrap();
2039 std::fs::write(dir.join("src/other.txt"), format!("other {c}\n")).unwrap();
2040 grit_test_support::git(dir, &["add", "."]);
2041 grit_test_support::git(
2042 dir,
2043 &[
2044 "-c",
2045 "user.name=T",
2046 "-c",
2047 "user.email=t@e",
2048 "commit",
2049 "-qm",
2050 "c",
2051 ],
2052 );
2053 }
2054 let tip = grit_test_support::git(dir, &["rev-parse", "main"]);
2055 let tip = ObjectId::from_hex(tip.trim()).unwrap();
2056 let repo = Repository::open(&dir.join(".git"), &RepositoryOptions::empty()).unwrap();
2057 (tmp, repo, tip)
2058 }
2059
2060 #[test]
2061 fn loose_plan_deltifies_trees_and_blobs_with_compressed_search_output() {
2062 let (tmp, repo, tip) = loose_history(12);
2063 assert!(!objects_dir_has_local_packs(repo.odb().objects_dir()));
2064 let empty = HashSet::new();
2065 let opts = PackBuildOptions::for_local_copy(None);
2066 let selection = crate::pack_object_select::enumerate_pack_objects_naming(
2067 repo.odb(),
2068 &[tip],
2069 &[],
2070 &PackEnumerateOptions {
2071 filter: None,
2072 shallow_grafts: &empty,
2073 have_shallow_grafts: Some(&empty),
2074 use_bitmaps: false,
2075 exclude_objects: None,
2076 peer_odb: None,
2077 have_boundary: HaveBoundary::CommitClosure,
2078 },
2079 NameTrees::for_delta_search(&opts),
2080 )
2081 .unwrap();
2082 let plan = plan_deltas(
2083 repo.odb(),
2084 &selection.send,
2085 &selection.have_closure,
2086 selection.peer_held.as_deref(),
2087 &opts,
2088 &selection.name_hashes,
2089 Some(1),
2090 )
2091 .unwrap();
2092 let deltas_of = |kind| {
2093 plan.entries
2094 .iter()
2095 .filter(|e| e.kind == kind && e.base.is_some())
2096 .count()
2097 };
2098 assert!(deltas_of(ObjectKind::Blob) >= 10, "blob deltas");
2099 assert!(deltas_of(ObjectKind::Tree) >= 10, "subtree deltas");
2100 assert!(plan
2101 .entries
2102 .iter()
2103 .filter(|e| e.base.is_some())
2104 .all(|e| e.searched_delta.is_some()));
2105
2106 let pack = crate::pack_delta::serialize_pack_with_deltas(
2107 repo.odb(),
2108 &plan.entries,
2109 &opts,
2110 Some(2),
2111 )
2112 .unwrap();
2113 std::fs::write(tmp.path().join("plan.pack"), &pack.bytes).unwrap();
2114 grit_test_support::git(tmp.path(), &["index-pack", "--strict", "plan.pack"]);
2115 }
2116
2117 #[test]
2118 fn serve_options_follow_pack_config() {
2119 use crate::config::{ConfigFile, ConfigScope, ConfigSet};
2120 let file = ConfigFile::parse(
2121 Path::new(".git/config"),
2122 "[pack]\n\twindow = 4\n\tdepth = 0\n",
2123 ConfigScope::Local,
2124 )
2125 .unwrap();
2126 let mut cfg = ConfigSet::new();
2127 cfg.merge(&file);
2128 let opts = PackBuildOptions::for_serve(Some(&cfg), true, false);
2129 assert_eq!((opts.window, opts.max_depth), (4, 0));
2130 assert!(opts.thin && !opts.use_ofs_delta && opts.delta);
2131 assert!(
2132 opts.reuse_deltas,
2133 "like git upload-pack, depth 0 stops new deltas but still sends on-disk deltas"
2134 );
2135
2136 let defaults = PackBuildOptions::for_serve(None, false, true);
2137 assert_eq!(
2138 (defaults.window, defaults.max_depth),
2139 (10, crate::pack::DEFAULT_PACK_DEPTH)
2140 );
2141 assert!(defaults.reuse_deltas && defaults.reuse_objects && !defaults.thin);
2142 }
2143}