1use crate::environment::RepositoryOptions;
7
8use std::collections::{HashMap, HashSet, VecDeque};
9use std::io::Write;
10use std::path::Path;
11
12use flate2::write::ZlibEncoder;
13use flate2::Compression;
14
15use crate::bitmap_walk::ReachableSet;
16use crate::delta_encode::{encode_prefix_extension_delta, DeltaIndex};
17use crate::error::{Error, Result};
18use crate::objects::{
19 empty_object_id_hash_set, oid_hash_map, oid_hash_set, oid_hash_set_from, parse_commit,
20 parse_tag, HashAlgo, ObjectId, ObjectInfo, ObjectKind, OidHashMap, OidHashSet, TreeRefIter,
21};
22use crate::odb::Odb;
23use crate::pack_object_select::{HaveBoundary, NameTrees};
24use crate::parallel::{try_par_map_force, ParallelHashError};
25use crate::repo::Repository;
26use crate::rev_list::ObjectFilter;
27
28#[derive(Clone, Copy, Debug)]
30pub struct PackBuildOptions {
31 pub thin: bool,
34 pub delta: bool,
36 pub window: usize,
38 pub max_depth: usize,
40 pub use_ofs_delta: bool,
42 pub respect_islands: bool,
44 pub reuse_deltas: bool,
46 pub reuse_objects: bool,
48 pub use_bitmaps: bool,
50}
51
52impl Default for PackBuildOptions {
53 fn default() -> Self {
54 Self {
55 thin: false,
56 delta: false,
57 window: 10,
58 max_depth: 50,
59 use_ofs_delta: true,
60 respect_islands: false,
61 reuse_deltas: true,
62 reuse_objects: true,
63 use_bitmaps: true,
64 }
65 }
66}
67
68impl PackBuildOptions {
69 #[must_use]
71 pub fn window_and_depth_from_config(cfg: Option<&crate::config::ConfigSet>) -> (usize, usize) {
72 cfg.map(|c| (c.pack_object_window(), c.pack_object_depth()))
73 .unwrap_or((10, crate::pack::DEFAULT_PACK_DEPTH))
74 }
75
76 #[must_use]
78 pub fn for_local_copy(cfg: Option<&crate::config::ConfigSet>) -> Self {
79 let (window, max_depth) = Self::window_and_depth_from_config(cfg);
80 Self {
81 thin: false,
82 delta: true,
83 window,
84 max_depth,
85 use_ofs_delta: true,
86 reuse_deltas: max_depth > 0,
87 reuse_objects: true,
88 use_bitmaps: true,
89 ..Default::default()
90 }
91 }
92
93 #[must_use]
96 pub fn for_repack_rewrite(cfg: Option<&crate::config::ConfigSet>) -> Self {
97 let max_depth = Self::window_and_depth_from_config(cfg).1;
98 Self {
99 thin: false,
100 delta: true,
101 window: 0,
102 max_depth,
103 use_ofs_delta: true,
104 reuse_deltas: max_depth > 0,
105 reuse_objects: true,
106 use_bitmaps: true,
107 ..Default::default()
108 }
109 }
110
111 #[must_use]
126 pub fn for_serve(
127 cfg: Option<&crate::config::ConfigSet>,
128 thin: bool,
129 use_ofs_delta: bool,
130 ) -> Self {
131 let (window, max_depth) = Self::window_and_depth_from_config(cfg);
132 Self {
133 thin,
134 delta: true,
135 window,
136 max_depth,
137 use_ofs_delta,
138 reuse_deltas: true,
139 reuse_objects: true,
140 use_bitmaps: true,
141 ..Default::default()
142 }
143 }
144
145 #[must_use]
150 pub fn for_local_push(cfg: Option<&crate::config::ConfigSet>) -> Self {
151 let (window, max_depth) = Self::window_and_depth_from_config(cfg);
152 Self {
153 thin: true,
154 delta: true,
155 window,
156 max_depth,
157 use_ofs_delta: true,
158 reuse_deltas: true,
159 reuse_objects: true,
160 use_bitmaps: true,
161 ..Default::default()
162 }
163 }
164}
165
166#[derive(Clone, Copy, Debug, Default)]
168pub struct PackObjectsOptions {
169 pub build: PackBuildOptions,
171 pub search_threads: Option<usize>,
173 pub write_threads: Option<usize>,
175}
176
177impl From<PackBuildOptions> for PackObjectsOptions {
178 fn from(build: PackBuildOptions) -> Self {
179 Self {
180 build,
181 search_threads: None,
182 write_threads: None,
183 }
184 }
185}
186
187#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
189pub struct PackStats {
190 pub object_count: u32,
192 pub pack_bytes: u64,
194 pub delta_count: u32,
196 pub verbatim_reuse_bytes: u64,
198 pub verbatim_reuse_objects: u32,
200 pub pack_ofs_fixup_objects: u32,
202 pub pack_ref_from_ofs_objects: u32,
204}
205
206#[derive(Clone, Copy, Debug, PartialEq, Eq)]
208pub enum PackProgress {
209 Enumerated {
211 total: usize,
213 },
214 PlannedDeltas,
216 WroteObject {
218 written: usize,
220 total: usize,
222 },
223}
224
225pub struct PackObjects<'a> {
227 odb: &'a Odb,
228 source_odb: &'a Odb,
229 opts: PackObjectsOptions,
230 wants: Vec<ObjectId>,
231 haves: Vec<ObjectId>,
232 have_shallow: OidHashSet,
233 source_shallow: OidHashSet,
234 progress: Option<Box<dyn FnMut(PackProgress)>>,
235}
236
237impl<'a> PackObjects<'a> {
238 #[must_use]
240 pub fn new(odb: &'a Odb, opts: PackObjectsOptions) -> Self {
241 Self {
242 odb,
243 source_odb: odb,
244 opts,
245 wants: Vec::new(),
246 haves: Vec::new(),
247 have_shallow: oid_hash_set(),
248 source_shallow: oid_hash_set(),
249 progress: None,
250 }
251 }
252
253 #[must_use]
255 pub fn with_source_odb(mut self, source: &'a Odb) -> Self {
256 self.source_odb = source;
257 self
258 }
259
260 #[must_use]
262 pub fn wants(mut self, wants: &[ObjectId]) -> Self {
263 self.wants.extend_from_slice(wants);
264 self
265 }
266
267 #[must_use]
269 pub fn haves(mut self, haves: &[ObjectId]) -> Self {
270 self.haves.extend_from_slice(haves);
271 self
272 }
273
274 #[must_use]
276 pub fn have_shallow_grafts(mut self, grafts: &HashSet<ObjectId>) -> Self {
277 self.have_shallow = grafts.iter().copied().collect();
278 self
279 }
280
281 #[must_use]
283 pub fn source_shallow_grafts(mut self, grafts: &HashSet<ObjectId>) -> Self {
284 self.source_shallow = grafts.iter().copied().collect();
285 self
286 }
287
288 #[must_use]
290 pub fn progress<F>(mut self, f: F) -> Self
291 where
292 F: FnMut(PackProgress) + 'static,
293 {
294 self.progress = Some(Box::new(f));
295 self
296 }
297
298 fn fire(&mut self, ev: PackProgress) {
299 if let Some(ref mut f) = self.progress {
300 f(ev);
301 }
302 }
303
304 pub fn write_to(mut self, w: &mut dyn Write) -> Result<PackStats> {
310 let opts = self.opts.build;
311 let have_shallow: HashSet<ObjectId> = self.have_shallow.iter().copied().collect();
312 let source_shallow: HashSet<ObjectId> = self.source_shallow.iter().copied().collect();
313 let peer_odb = (!std::ptr::eq(self.source_odb, self.odb)).then_some(self.odb);
314 let selection = crate::pack_object_select::enumerate_pack_objects_naming(
315 self.source_odb,
316 &self.wants,
317 &self.haves,
318 &crate::pack_object_select::PackEnumerateOptions {
319 filter: None,
320 shallow_grafts: &source_shallow,
321 have_shallow_grafts: Some(&have_shallow),
322 use_bitmaps: opts.use_bitmaps,
323 exclude_objects: None,
324 peer_odb,
325 have_boundary: HaveBoundary::CommitClosure,
326 },
327 NameTrees::for_delta_search(&opts),
328 )?;
329 let send = selection.send;
330 self.fire(PackProgress::Enumerated { total: send.len() });
331 if !opts.delta {
332 let bytes = serialize_pack(self.source_odb, &send, &opts)?;
333 w.write_all(&bytes).map_err(Error::Io)?;
334 let count = u32::from_be_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
335 return Ok(PackStats {
336 object_count: count,
337 pack_bytes: bytes.len() as u64,
338 ..Default::default()
339 });
340 }
341 let plan = plan_deltas(
342 self.source_odb,
343 &send,
344 &selection.have_closure,
345 selection.peer_held.as_deref(),
346 &opts,
347 &selection.name_hashes,
348 self.opts.search_threads,
349 )?;
350 self.fire(PackProgress::PlannedDeltas);
351 crate::pack_delta::write_pack_with_deltas(
352 self.source_odb,
353 &plan.entries,
354 &opts,
355 self.opts.write_threads,
356 w,
357 None,
358 )
359 }
360}
361
362pub fn reachable_closure(
365 odb: &Odb,
366 roots: &[ObjectId],
367 stop: &HashSet<ObjectId>,
368 skip_missing: bool,
369 shallow_grafts: &HashSet<ObjectId>,
370) -> Result<HashSet<ObjectId>> {
371 let stop = oid_hash_set_from(stop);
372 let shallow_grafts = oid_hash_set_from(shallow_grafts);
373 let mut seen = oid_hash_set();
374 let order = collect_reachable_excluding(odb, roots, &stop, skip_missing, &shallow_grafts)?;
375 for oid in order {
376 seen.insert(oid);
377 }
378 Ok(seen.into_iter().collect())
379}
380
381pub fn build_pack(
384 odb: &Odb,
385 wants: &[ObjectId],
386 haves: &[ObjectId],
387 opts: &PackBuildOptions,
388) -> Result<Vec<u8>> {
389 build_pack_with_shallow_filter_and_boundary(
390 odb,
391 wants,
392 haves,
393 empty_object_id_hash_set(),
394 empty_object_id_hash_set(),
395 None,
396 opts,
397 HaveBoundary::CommitClosure,
398 )
399}
400
401pub fn build_pack_for_push(
411 odb: &Odb,
412 wants: &[ObjectId],
413 haves: &[ObjectId],
414 opts: &PackBuildOptions,
415) -> Result<Vec<u8>> {
416 let mut pack = Vec::new();
417 write_pack_for_push(odb, wants, haves, opts, &mut pack)?;
418 Ok(pack)
419}
420
421pub fn write_pack_for_push(
444 odb: &Odb,
445 wants: &[ObjectId],
446 haves: &[ObjectId],
447 opts: &PackBuildOptions,
448 w: &mut dyn Write,
449) -> Result<PackStats> {
450 odb.with_pack_read_context(|| {
453 write_pack_with_shallow_and_filter(
454 odb,
455 wants,
456 haves,
457 empty_object_id_hash_set(),
458 empty_object_id_hash_set(),
459 None,
460 opts,
461 HaveBoundary::TipsOnly,
462 w,
463 )
464 })
465}
466
467pub fn build_pack_with_shallow_and_filter(
472 odb: &Odb,
473 wants: &[ObjectId],
474 haves: &[ObjectId],
475 have_shallow: &HashSet<ObjectId>,
476 source_shallow: &HashSet<ObjectId>,
477 filter: Option<&ObjectFilter>,
478 opts: &PackBuildOptions,
479) -> Result<Vec<u8>> {
480 build_pack_with_shallow_filter_and_boundary(
481 odb,
482 wants,
483 haves,
484 have_shallow,
485 source_shallow,
486 filter,
487 opts,
488 HaveBoundary::CommitClosure,
489 )
490}
491
492#[allow(clippy::too_many_arguments)]
493fn build_pack_with_shallow_filter_and_boundary(
494 odb: &Odb,
495 wants: &[ObjectId],
496 haves: &[ObjectId],
497 have_shallow: &HashSet<ObjectId>,
498 source_shallow: &HashSet<ObjectId>,
499 filter: Option<&ObjectFilter>,
500 opts: &PackBuildOptions,
501 have_boundary: HaveBoundary,
502) -> Result<Vec<u8>> {
503 let mut pack = Vec::new();
504 write_pack_with_shallow_and_filter(
505 odb,
506 wants,
507 haves,
508 have_shallow,
509 source_shallow,
510 filter,
511 opts,
512 have_boundary,
513 &mut pack,
514 )?;
515 Ok(pack)
516}
517
518#[allow(clippy::too_many_arguments)]
520fn write_pack_with_shallow_and_filter(
521 odb: &Odb,
522 wants: &[ObjectId],
523 haves: &[ObjectId],
524 have_shallow: &HashSet<ObjectId>,
525 source_shallow: &HashSet<ObjectId>,
526 filter: Option<&ObjectFilter>,
527 opts: &PackBuildOptions,
528 have_boundary: HaveBoundary,
529 w: &mut dyn Write,
530) -> Result<PackStats> {
531 let selection = crate::pack_object_select::enumerate_pack_objects_naming(
532 odb,
533 wants,
534 haves,
535 &crate::pack_object_select::PackEnumerateOptions {
536 filter,
537 shallow_grafts: source_shallow,
538 have_shallow_grafts: Some(have_shallow),
539 use_bitmaps: opts.use_bitmaps,
540 exclude_objects: None,
541 peer_odb: None,
542 have_boundary,
543 },
544 NameTrees::for_delta_search(opts),
545 )?;
546 if !opts.delta {
547 let bytes = serialize_pack(odb, &selection.send, opts)?;
548 w.write_all(&bytes).map_err(Error::Io)?;
549 return Ok(PackStats {
550 object_count: u32::try_from(selection.send.len()).unwrap_or(u32::MAX),
551 pack_bytes: bytes.len() as u64,
552 ..PackStats::default()
553 });
554 }
555 let plan = plan_deltas(
556 odb,
557 &selection.send,
558 &selection.have_closure,
559 selection.peer_held.as_deref(),
560 opts,
561 &selection.name_hashes,
562 None,
563 )?;
564 crate::pack_delta::write_pack_with_deltas(odb, &plan.entries, opts, None, w, None)
565}
566
567#[allow(clippy::too_many_arguments)]
576pub fn write_upload_pack_stream(
577 repo: &Repository,
578 wants: &[ObjectId],
579 haves: &[ObjectId],
580 source_shallow: &HashSet<ObjectId>,
581 have_shallow: &HashSet<ObjectId>,
582 filter: Option<&ObjectFilter>,
583 opts: &PackBuildOptions,
584 progress: &mut dyn FnMut(PackProgress),
585 w: &mut dyn Write,
586) -> Result<PackStats> {
587 repo.odb().with_pack_read_context(|| {
588 write_upload_pack_stream_in_context(
589 repo,
590 wants,
591 haves,
592 source_shallow,
593 have_shallow,
594 filter,
595 opts,
596 progress,
597 w,
598 )
599 })
600}
601
602#[allow(clippy::too_many_arguments)]
603fn write_upload_pack_stream_in_context(
604 repo: &Repository,
605 wants: &[ObjectId],
606 haves: &[ObjectId],
607 source_shallow: &HashSet<ObjectId>,
608 have_shallow: &HashSet<ObjectId>,
609 filter: Option<&ObjectFilter>,
610 opts: &PackBuildOptions,
611 progress: &mut dyn FnMut(PackProgress),
612 w: &mut dyn Write,
613) -> Result<PackStats> {
614 if opts.use_bitmaps && filter.is_none() {
615 if let Some(stats) = crate::pack_reuse::try_write_streaming_pack(
616 repo,
617 wants,
618 haves,
619 source_shallow,
620 have_shallow,
621 filter,
622 opts,
623 progress,
624 w,
625 )? {
626 return Ok(stats);
627 }
628 }
629 write_pack_with_shallow_and_filter(
632 repo.odb(),
633 wants,
634 haves,
635 have_shallow,
636 source_shallow,
637 filter,
638 opts,
639 HaveBoundary::TipsOnly,
640 w,
641 )
642}
643
644fn write_pack_header(w: &mut dyn Write, count: u32) -> Result<()> {
645 w.write_all(b"PACK").map_err(Error::Io)?;
646 w.write_all(&2u32.to_be_bytes()).map_err(Error::Io)?;
647 w.write_all(&count.to_be_bytes()).map_err(Error::Io)?;
648 Ok(())
649}
650
651pub(crate) fn write_delta_pack_stream(
653 odb: &Odb,
654 plan: &DeltaPlan,
655 opts: &PackBuildOptions,
656 w: &mut dyn Write,
657 reuse: Option<&mut crate::pack_reuse::StreamingReuse>,
658) -> Result<PackStats> {
659 let (pack_bytes_len, reuse_stats) = if let Some(reuse) = reuse {
660 let pack = serialize_delta_plan_stream(odb, plan, opts, Some(reuse))?;
661 let len = pack.bytes.len() as u64;
662 w.write_all(&pack.bytes).map_err(Error::Io)?;
663 (len, reuse.take_stats())
664 } else {
665 let pack = serialize_delta_plan_stream(odb, plan, opts, None)?;
666 let len = pack.bytes.len() as u64;
667 w.write_all(&pack.bytes).map_err(Error::Io)?;
668 (len, crate::pack_reuse::ReuseWriteStats::default())
669 };
670 let delta_count = plan.entries.iter().filter(|e| e.base.is_some()).count() as u32;
671 Ok(PackStats {
672 object_count: u32::try_from(plan.entries.len())
673 .map_err(|_| Error::CorruptObject("pack object count exceeds u32".to_owned()))?,
674 pack_bytes: pack_bytes_len,
675 delta_count,
676 verbatim_reuse_bytes: reuse_stats.verbatim_reuse_bytes,
677 verbatim_reuse_objects: reuse_stats.verbatim_reuse_objects,
678 pack_ofs_fixup_objects: reuse_stats.pack_ofs_fixup_objects,
679 pack_ref_from_ofs_objects: reuse_stats.pack_ref_from_ofs_objects,
680 })
681}
682
683fn write_delta_plan_stream_body(
684 w: &mut dyn Write,
685 odb: &Odb,
686 plan: &DeltaPlan,
687 opts: &PackBuildOptions,
688 reuse: Option<&mut crate::pack_reuse::StreamingReuse>,
689) -> Result<Vec<(ObjectId, u64, u32)>> {
690 let count = u32::try_from(plan.entries.len())
691 .map_err(|_| Error::CorruptObject("pack object count exceeds u32".to_owned()))?;
692 write_pack_header(w, count)?;
693 let mut index_rows = Vec::with_capacity(plan.entries.len());
694 write_planned_entries(w, odb, plan, opts, 12, reuse, &mut index_rows)?;
695 Ok(index_rows)
696}
697
698pub(crate) fn serialize_delta_plan_stream(
700 odb: &Odb,
701 plan: &DeltaPlan,
702 opts: &PackBuildOptions,
703 reuse: Option<&mut crate::pack_reuse::StreamingReuse>,
704) -> Result<SerializedDeltaPack> {
705 let mut buf = Vec::new();
706 let index_rows = write_delta_plan_stream_body(&mut buf, odb, plan, opts, reuse)?;
707 append_pack_trailer(&mut buf, odb.hash_algo());
708 Ok(SerializedDeltaPack {
709 bytes: buf,
710 index_rows,
711 staging_path: None,
712 })
713}
714
715fn write_planned_entries(
716 w: &mut dyn Write,
717 odb: &Odb,
718 plan: &DeltaPlan,
719 opts: &PackBuildOptions,
720 pack_body_start: u64,
721 mut reuse: Option<&mut crate::pack_reuse::StreamingReuse>,
722 index_rows: &mut Vec<(ObjectId, u64, u32)>,
723) -> Result<()> {
724 let algo = odb.hash_algo();
725 let compression = odb.pack_compression()?;
726 let mut oid_to_offset: OidHashMap<u64> = oid_hash_map();
727 let mut body_written = 0u64;
728
729 for entry in &plan.entries {
730 let start = pack_body_start + body_written;
731 let mut chunk = Vec::new();
732 if entry.base.is_none() {
733 if let Some(reuse) = reuse.as_mut() {
734 if let Some(bytes) = reuse.try_emit_whole(&entry.oid, opts)? {
735 chunk = bytes;
736 }
737 }
738 if chunk.is_empty() {
739 write_whole_pack_object_from_odb(&mut chunk, odb, entry.oid, entry.kind, opts)?;
740 }
741 w.write_all(&chunk).map_err(Error::Io)?;
742 index_rows.push((entry.oid, start, crc32fast::hash(&chunk)));
743 body_written += chunk.len() as u64;
744 oid_to_offset.insert(entry.oid, start);
745 continue;
746 }
747 let Some(base_oid) = entry.base else {
748 continue;
749 };
750 let in_pack_offset = oid_to_offset.get(&base_oid).copied();
751 if let Some((delta_size, pack_bytes, zlib_start, zlib_end)) = &entry.reused_delta_zlib {
752 let delta_len = usize::try_from(*delta_size)
753 .map_err(|_| Error::CorruptObject("reused delta size exceeds usize".to_owned()))?;
754 if let Some(base_off) = in_pack_offset.filter(|_| opts.use_ofs_delta) {
755 let dist = start.checked_sub(base_off).ok_or_else(|| {
756 Error::CorruptObject("ofs-delta distance underflow".to_owned())
757 })?;
758 encode_pack_object_header(&mut chunk, 6, delta_len);
759 encode_ofs_delta_distance(&mut chunk, dist);
760 if let Some(reuse) = reuse.as_mut() {
761 reuse.record_ofs_fixup();
762 }
763 } else {
764 encode_pack_object_header(&mut chunk, 7, delta_len);
765 chunk.extend_from_slice(base_oid.as_bytes());
766 }
767 chunk.extend_from_slice(&pack_bytes[*zlib_start..*zlib_end]);
768 if let Some(reuse) = reuse.as_mut() {
769 reuse.record_verbatim_zlib((zlib_end - zlib_start) as u64);
770 }
771 } else {
772 let computed;
773 let (delta_len, zlib): (usize, &[u8]) = match &entry.searched_delta {
774 Some((len, zlib)) => (*len, zlib),
775 None => {
776 let target_data = odb.read(&entry.oid)?.data;
777 let base_data = odb.read(&base_oid)?.data;
778 let delta = if target_data.starts_with(&base_data)
779 && target_data.len() > base_data.len()
780 {
781 encode_prefix_extension_delta(&base_data, &target_data)?
782 } else {
783 DeltaIndex::new(&base_data)
784 .encode(&target_data, 0)
785 .ok_or_else(|| {
786 Error::CorruptObject(
787 "failed to encode pack delta for object".into(),
788 )
789 })?
790 };
791 let mut buf = Vec::new();
792 write_zlib(&mut buf, &delta, compression)?;
793 computed = buf;
794 (delta.len(), &computed)
795 }
796 };
797 if let Some(base_off) = in_pack_offset.filter(|_| opts.use_ofs_delta) {
798 let dist = start.checked_sub(base_off).ok_or_else(|| {
799 Error::CorruptObject("ofs-delta distance underflow".to_owned())
800 })?;
801 encode_pack_object_header(&mut chunk, 6, delta_len);
802 encode_ofs_delta_distance(&mut chunk, dist);
803 } else {
804 encode_pack_object_header(&mut chunk, 7, delta_len);
805 if base_oid.as_bytes().len() != algo.len() {
806 return Err(Error::CorruptObject(
807 "ref-delta base oid width mismatch".to_owned(),
808 ));
809 }
810 chunk.extend_from_slice(base_oid.as_bytes());
811 }
812 chunk.extend_from_slice(zlib);
813 }
814 w.write_all(&chunk).map_err(Error::Io)?;
815 index_rows.push((entry.oid, start, crc32fast::hash(&chunk)));
816 body_written += chunk.len() as u64;
817 oid_to_offset.insert(entry.oid, start);
818 }
819 Ok(())
820}
821
822pub fn build_pack_from_send_list(
828 odb: &Odb,
829 send: &[ObjectId],
830 have_closure: &HashSet<ObjectId>,
831 opts: &PackBuildOptions,
832 blob_name_hashes: &HashMap<ObjectId, u32>,
833) -> Result<Vec<u8>> {
834 build_pack_from_send_list_with_parallel_threads(
835 odb,
836 send,
837 have_closure,
838 opts,
839 blob_name_hashes,
840 None,
841 None,
842 )
843}
844
845pub fn build_pack_with_write_threads(
849 odb: &Odb,
850 wants: &[ObjectId],
851 haves: &[ObjectId],
852 opts: &PackBuildOptions,
853 write_threads: Option<usize>,
854) -> Result<Vec<u8>> {
855 build_pack_with_write_threads_full(odb, wants, haves, opts, write_threads)
856 .map(|pack| pack.bytes)
857}
858
859pub(crate) fn build_pack_with_write_threads_full(
861 odb: &Odb,
862 wants: &[ObjectId],
863 haves: &[ObjectId],
864 opts: &PackBuildOptions,
865 write_threads: Option<usize>,
866) -> Result<SerializedDeltaPack> {
867 let empty_shallow = empty_object_id_hash_set();
868 let have_closure = reachable_closure(odb, haves, empty_shallow, true, empty_shallow)?;
869 let selection = crate::pack_object_select::enumerate_pack_objects_naming(
870 odb,
871 wants,
872 haves,
873 &crate::pack_object_select::PackEnumerateOptions {
874 filter: None,
875 shallow_grafts: empty_shallow,
876 have_shallow_grafts: Some(empty_shallow),
877 use_bitmaps: opts.use_bitmaps,
878 exclude_objects: Some(&have_closure),
879 peer_odb: None,
880 have_boundary: HaveBoundary::CommitClosure,
881 },
882 NameTrees::for_delta_search(opts),
883 )?;
884 build_pack_from_send_list_full(
885 odb,
886 &selection.send,
887 &selection.have_closure,
888 opts,
889 &selection.name_hashes,
890 None,
891 write_threads,
892 )
893}
894
895pub fn build_pack_with_parallel_threads(
897 odb: &Odb,
898 wants: &[ObjectId],
899 haves: &[ObjectId],
900 opts: &PackBuildOptions,
901 search_threads: Option<usize>,
902 write_threads: Option<usize>,
903) -> Result<Vec<u8>> {
904 let empty_shallow = empty_object_id_hash_set();
905 let have_closure = reachable_closure(odb, haves, empty_shallow, true, empty_shallow)?;
906 let selection = crate::pack_object_select::enumerate_pack_objects_naming(
907 odb,
908 wants,
909 haves,
910 &crate::pack_object_select::PackEnumerateOptions {
911 filter: None,
912 shallow_grafts: empty_shallow,
913 have_shallow_grafts: Some(empty_shallow),
914 use_bitmaps: opts.use_bitmaps,
915 exclude_objects: Some(&have_closure),
916 peer_odb: None,
917 have_boundary: HaveBoundary::CommitClosure,
918 },
919 NameTrees::for_delta_search(opts),
920 )?;
921 build_pack_from_send_list_with_parallel_threads(
922 odb,
923 &selection.send,
924 &selection.have_closure,
925 opts,
926 &selection.name_hashes,
927 search_threads,
928 write_threads,
929 )
930}
931
932pub fn build_pack_from_send_list_with_write_threads(
934 odb: &Odb,
935 send: &[ObjectId],
936 have_closure: &HashSet<ObjectId>,
937 opts: &PackBuildOptions,
938 blob_name_hashes: &HashMap<ObjectId, u32>,
939 write_threads: Option<usize>,
940) -> Result<Vec<u8>> {
941 build_pack_from_send_list_with_parallel_threads(
942 odb,
943 send,
944 have_closure,
945 opts,
946 blob_name_hashes,
947 None,
948 write_threads,
949 )
950}
951
952pub fn build_pack_from_send_list_with_parallel_threads(
954 odb: &Odb,
955 send: &[ObjectId],
956 have_closure: &HashSet<ObjectId>,
957 opts: &PackBuildOptions,
958 blob_name_hashes: &HashMap<ObjectId, u32>,
959 search_threads: Option<usize>,
960 write_threads: Option<usize>,
961) -> Result<Vec<u8>> {
962 build_pack_from_send_list_full(
963 odb,
964 send,
965 have_closure,
966 opts,
967 blob_name_hashes,
968 search_threads,
969 write_threads,
970 )
971 .map(|pack| pack.bytes)
972}
973
974pub(crate) fn build_pack_from_send_list_full(
975 odb: &Odb,
976 send: &[ObjectId],
977 have_closure: &HashSet<ObjectId>,
978 opts: &PackBuildOptions,
979 blob_name_hashes: &HashMap<ObjectId, u32>,
980 search_threads: Option<usize>,
981 write_threads: Option<usize>,
982) -> Result<SerializedDeltaPack> {
983 if !opts.delta {
984 let bytes = serialize_pack(odb, send, opts)?;
985 return Ok(SerializedDeltaPack {
986 bytes,
987 index_rows: Vec::new(),
988 staging_path: None,
989 });
990 }
991 let plan = plan_deltas(
992 odb,
993 send,
994 have_closure,
995 None,
996 opts,
997 blob_name_hashes,
998 search_threads,
999 )?;
1000 crate::pack_delta::serialize_pack_with_deltas(odb, &plan.entries, opts, write_threads)
1001}
1002
1003pub(crate) fn build_pack_for_local_fetch(
1005 source_odb: &Odb,
1006 wants: &[ObjectId],
1007 have_odb: &Odb,
1008 haves: &[ObjectId],
1009 source_shallow: &HashSet<ObjectId>,
1010 have_shallow: &HashSet<ObjectId>,
1011 opts: &PackBuildOptions,
1012) -> Result<Vec<u8>> {
1013 let mut buf = Vec::new();
1014 PackObjects::new(have_odb, PackObjectsOptions::from(*opts))
1015 .with_source_odb(source_odb)
1016 .wants(wants)
1017 .haves(haves)
1018 .have_shallow_grafts(have_shallow)
1019 .source_shallow_grafts(source_shallow)
1020 .write_to(&mut buf)?;
1021 Ok(buf)
1022}
1023
1024fn collect_reachable_excluding(
1025 odb: &Odb,
1026 roots: &[ObjectId],
1027 exclude: &OidHashSet,
1028 skip_missing: bool,
1029 shallow_grafts: &OidHashSet,
1030) -> Result<Vec<ObjectId>> {
1031 let mut visited: OidHashSet = oid_hash_set();
1032 let mut ordered: Vec<ObjectId> = Vec::new();
1033 let mut queue: VecDeque<ObjectId> = VecDeque::new();
1034
1035 let enqueue = |oid: ObjectId,
1036 queue: &mut VecDeque<ObjectId>,
1037 visited: &mut OidHashSet,
1038 ordered: &mut Vec<ObjectId>|
1039 -> bool {
1040 if !visited.insert(oid) {
1041 return false;
1042 }
1043 if !exclude.contains(&oid) {
1044 ordered.push(oid);
1045 }
1046 queue.push_back(oid);
1047 true
1048 };
1049
1050 for &root in roots {
1051 enqueue(root, &mut queue, &mut visited, &mut ordered);
1052 }
1053
1054 let oid_len = odb.hash_algo().len();
1055
1056 while let Some(oid) = queue.pop_front() {
1057 let obj = match odb.read(&oid) {
1058 Ok(o) => o,
1059 Err(_) if skip_missing => continue,
1063 Err(e) => return Err(e),
1064 };
1065 match obj.kind {
1066 ObjectKind::Commit => {
1067 let commit = parse_commit(&obj.data)?;
1068 if !shallow_grafts.contains(&oid) {
1069 for parent in commit.parents {
1070 enqueue(parent, &mut queue, &mut visited, &mut ordered);
1071 }
1072 }
1073 enqueue(commit.tree, &mut queue, &mut visited, &mut ordered);
1074 }
1075 ObjectKind::Tree => {
1076 for entry in TreeRefIter::with_oid_len(&obj.data, oid_len) {
1077 let entry = entry?;
1078 if entry.mode == 0o160000 {
1081 continue;
1082 }
1083 enqueue(entry.oid, &mut queue, &mut visited, &mut ordered);
1084 }
1085 }
1086 ObjectKind::Tag => {
1087 let tag = parse_tag(&obj.data)?;
1088 enqueue(tag.object, &mut queue, &mut visited, &mut ordered);
1089 }
1090 ObjectKind::Blob => {}
1091 }
1092 }
1093
1094 Ok(ordered)
1095}
1096
1097pub(crate) fn pack_type_code(kind: ObjectKind) -> u8 {
1099 match kind {
1100 ObjectKind::Commit => 1,
1101 ObjectKind::Tree => 2,
1102 ObjectKind::Blob => 3,
1103 ObjectKind::Tag => 4,
1104 }
1105}
1106
1107pub(crate) fn encode_pack_object_header(buf: &mut Vec<u8>, type_code: u8, payload_len: usize) {
1110 let mut size = payload_len;
1111 let first = ((type_code & 0x7) << 4) | (size & 0x0f) as u8;
1112 size >>= 4;
1113 if size > 0 {
1114 buf.push(first | 0x80);
1115 while size > 0 {
1116 let b = (size & 0x7f) as u8;
1117 size >>= 7;
1118 buf.push(if size > 0 { b | 0x80 } else { b });
1119 }
1120 } else {
1121 buf.push(first);
1122 }
1123}
1124
1125fn serialize_pack(odb: &Odb, oids: &[ObjectId], opts: &PackBuildOptions) -> Result<Vec<u8>> {
1128 let mut buf = Vec::new();
1129 buf.extend_from_slice(b"PACK");
1130 buf.extend_from_slice(&2u32.to_be_bytes());
1131 let count = u32::try_from(oids.len())
1132 .map_err(|_| Error::CorruptObject("pack object count exceeds u32".to_owned()))?;
1133 buf.extend_from_slice(&count.to_be_bytes());
1134
1135 for oid in oids {
1136 let kind = odb.read_info(oid)?.kind;
1137 write_whole_pack_object_from_odb(&mut buf, odb, *oid, kind, opts)?;
1138 }
1139
1140 append_pack_trailer(&mut buf, odb.hash_algo());
1141 Ok(buf)
1142}
1143
1144fn write_whole_pack_object_from_odb(
1146 buf: &mut Vec<u8>,
1147 odb: &Odb,
1148 oid: ObjectId,
1149 kind: ObjectKind,
1150 opts: &PackBuildOptions,
1151) -> Result<()> {
1152 if opts.reuse_objects && odb.hash_algo() == HashAlgo::Sha1 {
1153 if let Some(raw) = crate::pack::packed_full_object_slice(odb.objects_dir(), &oid)? {
1154 buf.extend_from_slice(&raw);
1155 return Ok(());
1156 }
1157 }
1158 let size = odb.read_info(&oid)?.size;
1159 let stream_from = odb.big_file_threshold()?;
1160 if kind == ObjectKind::Blob && size >= stream_from {
1161 return odb
1162 .with_stream(&oid, |mut stream| {
1163 let payload_len = usize::try_from(stream.size).map_err(|_| {
1164 Error::CorruptObject(format!("object size overflow for {}", oid.to_hex()))
1165 })?;
1166 encode_pack_object_header(buf, pack_type_code(kind), payload_len);
1167 write_zlib_from_reader(buf, &mut stream.reader, odb.pack_compression()?)
1168 })?
1169 .ok_or_else(|| Error::ObjectNotFound(oid.to_hex()));
1170 }
1171 let obj = odb.read(&oid)?;
1172 write_whole_pack_object(buf, odb, oid, kind, &obj.data, opts)
1173}
1174
1175fn write_whole_pack_object(
1177 buf: &mut Vec<u8>,
1178 odb: &Odb,
1179 oid: ObjectId,
1180 kind: ObjectKind,
1181 data: &[u8],
1182 opts: &PackBuildOptions,
1183) -> Result<()> {
1184 if opts.reuse_objects && odb.hash_algo() == HashAlgo::Sha1 {
1185 if let Some(raw) = crate::pack::packed_full_object_slice(odb.objects_dir(), &oid)? {
1186 buf.extend_from_slice(&raw);
1187 return Ok(());
1188 }
1189 }
1190 encode_pack_object_header(buf, pack_type_code(kind), data.len());
1191 let compression = odb.pack_compression()?;
1192 write_zlib(buf, data, compression)
1193}
1194
1195pub(crate) fn append_pack_trailer(buf: &mut Vec<u8>, algo: HashAlgo) {
1198 buf.extend_from_slice(&algo.checksum_digest(&*buf));
1199}
1200
1201pub(crate) fn append_whole_object_from_odb(
1205 buf: &mut Vec<u8>,
1206 odb: &Odb,
1207 oid: ObjectId,
1208) -> Result<ObjectKind> {
1209 let obj = odb.read(&oid)?;
1210 write_whole_pack_object(
1211 buf,
1212 odb,
1213 oid,
1214 obj.kind,
1215 &obj.data,
1216 &PackBuildOptions::default(),
1217 )?;
1218 Ok(obj.kind)
1219}
1220
1221pub(crate) struct PlannedEntry {
1223 pub(crate) oid: ObjectId,
1224 pub(crate) kind: ObjectKind,
1225 pub(crate) base: Option<ObjectId>,
1228 pub(crate) searched_delta: Option<(usize, Vec<u8>)>,
1232 pub(crate) reused_delta_zlib:
1235 Option<(u64, std::sync::Arc<crate::pack::PackData>, usize, usize)>,
1236}
1237
1238pub(crate) struct DeltaPlan {
1241 pub(crate) entries: Vec<PlannedEntry>,
1242}
1243
1244pub(crate) struct SerializedDeltaPack {
1246 pub bytes: Vec<u8>,
1247 pub index_rows: Vec<(ObjectId, u64, u32)>,
1248 pub staging_path: Option<std::path::PathBuf>,
1250}
1251
1252fn read_object_infos(
1254 odb: &Odb,
1255 oids: &[ObjectId],
1256 search_threads: Option<usize>,
1257) -> Result<Vec<ObjectInfo>> {
1258 let par = crate::pack_delta::pack_delta_search_parallel_options(odb, search_threads)?;
1259 let read = |oid: &ObjectId| odb.read_info(oid);
1260 if par.threads.get() <= 1 || oids.len() < 32 {
1261 return oids.iter().map(read).collect();
1262 }
1263 try_par_map_force(oids, par, read).map_err(|e| match e {
1264 ParallelHashError::Task(err) => err,
1265 })
1266}
1267
1268fn order_bases_first(entries: &mut [PlannedEntry]) {
1274 let n = entries.len();
1275 let bases: Vec<Option<usize>> = {
1276 let position: OidHashMap<usize> = entries
1277 .iter()
1278 .enumerate()
1279 .map(|(i, e)| (e.oid, i))
1280 .collect();
1281 entries
1282 .iter()
1283 .map(|e| e.base.and_then(|b| position.get(&b).copied()))
1284 .collect()
1285 };
1286 let mut dest = vec![usize::MAX; n];
1288 let mut next = 0usize;
1289 let mut chain = Vec::new();
1290 for i in 0..n {
1291 let mut cur = Some(i);
1292 while let Some(j) = cur {
1293 if dest[j] != usize::MAX || chain.len() > n {
1294 break;
1295 }
1296 chain.push(j);
1297 cur = bases[j];
1298 }
1299 while let Some(j) = chain.pop() {
1300 if dest[j] == usize::MAX {
1301 dest[j] = next;
1302 next += 1;
1303 }
1304 }
1305 }
1306 for i in 0..n {
1307 while dest[i] != i {
1308 let d = dest[i];
1309 entries.swap(i, d);
1310 dest.swap(i, d);
1311 }
1312 }
1313}
1314
1315pub(crate) fn objects_dir_has_local_packs(objects_dir: &Path) -> bool {
1317 let pack_dir = objects_dir.join("pack");
1318 if !pack_dir.is_dir() {
1319 return false;
1320 }
1321 std::fs::read_dir(&pack_dir)
1322 .ok()
1323 .into_iter()
1324 .flatten()
1325 .filter_map(|e| e.ok())
1326 .any(|e| e.path().extension().is_some_and(|ext| ext == "pack"))
1327}
1328
1329pub(crate) fn plan_deltas(
1360 odb: &Odb,
1361 send: &[ObjectId],
1362 peer_commits: &HashSet<ObjectId>,
1363 peer_held: Option<&ReachableSet>,
1364 opts: &PackBuildOptions,
1365 name_hashes: &HashMap<ObjectId, u32>,
1366 search_threads: Option<usize>,
1367) -> Result<DeltaPlan> {
1368 let has_local_packs = objects_dir_has_local_packs(odb.objects_dir());
1369 let reuse_on_disk = opts.reuse_deltas && odb.hash_algo() == HashAlgo::Sha1 && has_local_packs;
1370 let search = opts.window > 0 && opts.max_depth > 0;
1371 let infos = read_object_infos(odb, send, search_threads)?;
1372 let in_pack: HashSet<ObjectId> = if reuse_on_disk || opts.thin || opts.respect_islands {
1373 send.iter().copied().collect()
1374 } else {
1375 HashSet::new()
1376 };
1377
1378 let islands = load_islands_for_pack(odb, &in_pack, opts);
1382
1383 let peer_has = |oid: &ObjectId| peer_held.is_some_and(|held| held.contains(oid));
1385 let thin_bases = if opts.thin && (reuse_on_disk || search) {
1386 crate::pack_thin_bases::find_thin_bases(
1387 odb,
1388 send,
1389 &infos,
1390 &|oid| in_pack.contains(oid),
1391 &|oid| peer_commits.contains(oid) || peer_has(oid),
1392 name_hashes,
1393 opts.window.max(1),
1394 )?
1395 } else {
1396 crate::pack_thin_bases::ThinBases::default()
1397 };
1398
1399 let mut delta_to_base: OidHashMap<ObjectId> = oid_hash_map();
1401 let mut reused_zlib: HashMap<ObjectId, crate::pack::OnDiskReuseZlibEntry> = HashMap::new();
1403
1404 if reuse_on_disk {
1406 let reusable_base = |oid: &ObjectId| {
1409 in_pack.contains(oid) || (opts.thin && (thin_bases.oids.contains(oid) || peer_has(oid)))
1410 };
1411 reused_zlib = crate::pack::collect_packed_delta_reuse_for_send(
1412 odb.objects_dir(),
1413 &in_pack,
1414 &reusable_base,
1415 )?;
1416 reused_zlib.retain(|t, (base, ..)| {
1417 let far_peer_base = !in_pack.contains(base) && !thin_bases.oids.contains(base);
1420 !(far_peer_base && search && thin_bases.targets.contains(t))
1421 && *base != *t
1422 && islands.in_same_island(t, base)
1423 });
1424 delta_to_base.extend(reused_zlib.iter().map(|(t, (base, ..))| (*t, *base)));
1425 for (oid, info) in send.iter().zip(&infos) {
1427 if info.kind == ObjectKind::Blob && info.size == 0 && reused_zlib.remove(oid).is_some()
1428 {
1429 delta_to_base.remove(oid);
1430 }
1431 }
1432 }
1433
1434 let reuse_dependents: OidHashMap<usize> = {
1439 let mut longest: OidHashMap<usize> = oid_hash_map();
1440 for target in reused_zlib.keys() {
1441 let mut cur = *target;
1442 let mut depth = 0usize;
1443 while let Some(base) = delta_to_base.get(&cur) {
1444 depth += 1;
1445 cur = *base;
1446 if depth > delta_to_base.len() {
1447 break;
1448 }
1449 }
1450 let entry = longest.entry(cur).or_insert(0);
1451 *entry = (*entry).max(depth);
1452 }
1453 longest
1454 };
1455
1456 let mut searched: OidHashMap<(usize, Vec<u8>)> = oid_hash_map();
1457 if search {
1458 let found = search_new_deltas(
1459 odb,
1460 send,
1461 &infos,
1462 &thin_bases,
1463 |oid| {
1464 delta_to_base.contains_key(oid)
1465 || (thin_bases.oids.is_empty() && reuse_dependents.contains_key(oid))
1466 },
1467 &reuse_dependents,
1468 name_hashes,
1469 &islands,
1470 opts,
1471 search_threads,
1472 has_local_packs,
1473 )?;
1474 for (target, base, delta) in found {
1475 delta_to_base.insert(target, base);
1476 searched.insert(target, delta);
1477 }
1478 }
1479 drop((in_pack, thin_bases));
1480
1481 if opts.max_depth > 0 && !delta_to_base.is_empty() {
1482 let mut delta_std: HashMap<ObjectId, ObjectId> = delta_to_base.drain().collect();
1484 crate::pack::apply_delta_depth_limit(&mut delta_std, opts.max_depth);
1485 delta_to_base.extend(delta_std);
1486 }
1487
1488 let mut entries: Vec<PlannedEntry> = send
1492 .iter()
1493 .zip(&infos)
1494 .map(|(&oid, info)| {
1495 let base = delta_to_base.remove(&oid);
1496 let (searched_delta, reused_delta_zlib) = match base {
1497 Some(_) => (
1498 searched.remove(&oid),
1499 reused_zlib
1500 .remove(&oid)
1501 .map(|(_, size, bytes, start, end)| (size, bytes, start, end)),
1502 ),
1503 None => (None, None),
1504 };
1505 PlannedEntry {
1506 oid,
1507 kind: info.kind,
1508 base,
1509 searched_delta,
1510 reused_delta_zlib,
1511 }
1512 })
1513 .collect();
1514 drop((infos, delta_to_base, searched, reused_zlib));
1515 order_bases_first(&mut entries);
1516 Ok(DeltaPlan { entries })
1517}
1518
1519type SearchedDelta = (ObjectId, ObjectId, (usize, Vec<u8>));
1521
1522#[allow(clippy::too_many_arguments)]
1531fn search_new_deltas(
1532 odb: &Odb,
1533 send: &[ObjectId],
1534 infos: &[ObjectInfo],
1535 thin_bases: &crate::pack_thin_bases::ThinBases,
1536 skip: impl Fn(&ObjectId) -> bool,
1537 reuse_dependents: &OidHashMap<usize>,
1538 name_hashes: &HashMap<ObjectId, u32>,
1539 islands: &crate::delta_islands::DeltaIslands,
1540 opts: &PackBuildOptions,
1541 search_threads: Option<usize>,
1542 has_local_packs: bool,
1543) -> Result<Vec<SearchedDelta>> {
1544 u32::try_from(send.len() + thin_bases.bases.len())
1545 .map_err(|_| Error::CorruptObject("too many objects for one pack".to_owned()))?;
1546 let ids: Vec<u32> = (0..send.len())
1547 .filter(|&i| infos[i].size > 0 && !skip(&send[i]))
1548 .map(|i| i as u32)
1549 .collect();
1550 let packed = if opts.reuse_deltas && has_local_packs {
1552 let oids: OidHashSet = ids.iter().map(|&i| send[i as usize]).collect();
1553 crate::pack::locate_packed_objects(odb.objects_dir(), &oids).ok()
1554 } else {
1555 None
1556 };
1557 let base_oids: Vec<ObjectId> = thin_bases.bases.iter().map(|b| b.oid).collect();
1558 let base_infos = read_object_infos(odb, &base_oids, search_threads)?;
1559 let mut candidates: Vec<crate::delta_search::DeltaCandidate> = ids
1560 .into_iter()
1561 .map(|id| {
1562 let oid = send[id as usize];
1563 let info = infos[id as usize];
1564 let slot = packed.as_ref().and_then(|p| p.get(&oid));
1565 crate::delta_search::DeltaCandidate {
1566 id,
1567 oid,
1568 kind: info.kind,
1569 size: info.size,
1570 name_hash: name_hashes.get(&oid).copied().unwrap_or(0),
1571 whole_in_pack: slot.filter(|s| s.whole).map(|s| s.pack),
1572 in_pack: slot.map(|s| s.pack),
1573 dependent_depth: reuse_dependents.get(&oid).copied().unwrap_or(0),
1574 base_only: false,
1575 }
1576 })
1577 .collect();
1578 drop(packed);
1579 candidates.extend(
1580 (send.len()..)
1581 .zip(thin_bases.bases.iter().zip(&base_infos))
1582 .filter(|(_, (base, info))| info.kind == base.kind && info.size > 0)
1583 .map(|(id, (base, info))| crate::delta_search::DeltaCandidate {
1584 id: id as u32,
1585 oid: base.oid,
1586 kind: info.kind,
1587 size: info.size,
1588 name_hash: base.name_hash,
1589 whole_in_pack: None,
1590 in_pack: None,
1591 dependent_depth: 0,
1592 base_only: true,
1593 }),
1594 );
1595 let oid_of = |id: u32| {
1596 let id = id as usize;
1597 send.get(id)
1598 .copied()
1599 .unwrap_or_else(|| base_oids[id - send.len()])
1600 };
1601 let window_memory = crate::pack_delta::pack_config(odb)?.pack_window_memory_bytes();
1602 let threads = crate::pack_delta::pack_delta_search_parallel_options(odb, search_threads)?;
1603 let allowed = |target: &ObjectId, base: &ObjectId| {
1604 islands.in_same_island(target, base)
1605 && (!reuse_dependents.contains_key(target) || thin_bases.oids.contains(base))
1606 };
1607 let found = crate::delta_search::search_deltas(
1608 odb,
1609 candidates,
1610 crate::delta_search::DeltaSearchParams {
1611 window: opts.window,
1612 max_depth: opts.max_depth,
1613 window_memory,
1614 hash_len: odb.hash_algo().len(),
1615 compression: odb.pack_compression()?,
1616 threads: threads.threads.get(),
1617 },
1618 &allowed,
1619 )?;
1620 Ok(found
1621 .into_iter()
1622 .map(|d| (oid_of(d.target), oid_of(d.base), (d.delta_len, d.zlib)))
1623 .collect())
1624}
1625
1626fn load_islands_for_pack(
1632 odb: &Odb,
1633 in_pack: &HashSet<ObjectId>,
1634 opts: &PackBuildOptions,
1635) -> crate::delta_islands::DeltaIslands {
1636 if !opts.respect_islands {
1637 return crate::delta_islands::DeltaIslands::default();
1638 }
1639 let Some(git_dir) = odb.config_git_dir() else {
1640 return crate::delta_islands::DeltaIslands::default();
1641 };
1642 let Ok(repo) = crate::repo::Repository::open(git_dir, &RepositoryOptions::empty()) else {
1643 return crate::delta_islands::DeltaIslands::default();
1644 };
1645 let env = crate::environment::Environment::empty();
1646 let cfg = crate::config::ConfigSet::load(&env, Some(git_dir), true).unwrap_or_default();
1647 crate::delta_islands::load_delta_islands(&repo, &cfg, in_pack)
1648}
1649
1650fn write_zlib(buf: &mut Vec<u8>, data: &[u8], compression: Compression) -> Result<()> {
1652 let mut enc = ZlibEncoder::new(Vec::new(), compression);
1653 enc.write_all(data).map_err(Error::Io)?;
1654 let compressed = enc.finish().map_err(Error::Io)?;
1655 buf.extend_from_slice(&compressed);
1656 Ok(())
1657}
1658
1659fn write_zlib_from_reader(
1661 buf: &mut Vec<u8>,
1662 reader: &mut dyn std::io::Read,
1663 compression: Compression,
1664) -> Result<()> {
1665 let mut enc = ZlibEncoder::new(Vec::new(), compression);
1666 std::io::copy(reader, &mut enc).map_err(Error::Io)?;
1667 let compressed = enc.finish().map_err(Error::Io)?;
1668 buf.extend_from_slice(&compressed);
1669 Ok(())
1670}
1671
1672pub(crate) fn encode_ofs_delta_distance(buf: &mut Vec<u8>, mut ofs: u64) {
1675 let mut dheader = [0u8; 32];
1676 let mut pos = dheader.len() - 1;
1677 dheader[pos] = (ofs & 0x7f) as u8;
1678 while {
1679 ofs >>= 7;
1680 ofs != 0
1681 } {
1682 pos -= 1;
1683 ofs -= 1;
1684 dheader[pos] = 0x80 | ((ofs & 0x7f) as u8);
1685 }
1686 buf.extend_from_slice(&dheader[pos..]);
1687}
1688
1689#[cfg(test)]
1690#[allow(clippy::unwrap_used, clippy::expect_used)]
1691pub(crate) mod tests {
1692 use super::*;
1693 use crate::pack_object_select::{enumerate_pack_objects, PackEnumerateOptions};
1694
1695 fn planned(n: u8, base: Option<u8>) -> PlannedEntry {
1696 let oid = |n: u8| ObjectId::from_bytes(&[n; 20]).unwrap();
1697 PlannedEntry {
1698 oid: oid(n),
1699 kind: ObjectKind::Blob,
1700 base: base.map(oid),
1701 searched_delta: None,
1702 reused_delta_zlib: None,
1703 }
1704 }
1705
1706 fn order_of(mut entries: Vec<PlannedEntry>) -> Vec<u8> {
1707 order_bases_first(&mut entries);
1708 entries.iter().map(|e| e.oid.as_bytes()[0]).collect()
1709 }
1710
1711 #[test]
1712 fn bases_are_ordered_before_their_targets() {
1713 let entries = vec![
1715 planned(1, Some(3)),
1716 planned(2, None),
1717 planned(3, Some(5)),
1718 planned(4, Some(9)),
1719 planned(5, None),
1720 planned(6, Some(2)),
1721 ];
1722 assert_eq!(order_of(entries), [5, 3, 1, 2, 4, 6]);
1723 }
1724
1725 #[test]
1726 fn base_cycles_do_not_hang_or_drop_entries() {
1727 let entries = vec![planned(1, Some(2)), planned(2, Some(1)), planned(3, None)];
1728 let mut order = order_of(entries);
1729 order.sort_unstable();
1730 assert_eq!(order, [1, 2, 3]);
1731 }
1732
1733 pub(crate) fn packed_selection(
1735 commits: usize,
1736 ) -> (
1737 tempfile::TempDir,
1738 Repository,
1739 crate::pack_object_select::PackObjectSelection,
1740 ) {
1741 let tmp = tempfile::tempdir().unwrap();
1742 let path = grit_test_support::git_packed_history(tmp.path(), commits);
1743 let tip = grit_test_support::git(&path, &["rev-parse", "main"]);
1744 let tip = ObjectId::from_hex(tip.trim()).unwrap();
1745 let repo = Repository::open(&path, &RepositoryOptions::empty()).unwrap();
1746 let empty = HashSet::new();
1747 let selection = enumerate_pack_objects(
1748 repo.odb(),
1749 &[tip],
1750 &[],
1751 &PackEnumerateOptions {
1752 filter: None,
1753 shallow_grafts: &empty,
1754 have_shallow_grafts: Some(&empty),
1755 use_bitmaps: false,
1756 exclude_objects: None,
1757 peer_odb: None,
1758 have_boundary: HaveBoundary::CommitClosure,
1759 },
1760 )
1761 .unwrap();
1762 (tmp, repo, selection)
1763 }
1764
1765 #[cfg_attr(
1766 not(feature = "full-tests"),
1767 ignore = "starts system git or is heavy; run with --features full-tests"
1768 )]
1769 #[test]
1770 fn serve_plan_reuses_on_disk_deltas() {
1771 let (_tmp, repo, selection) = packed_selection(80);
1772 let opts = PackBuildOptions::for_serve(None, false, true);
1773 let plan = plan_deltas(
1774 repo.odb(),
1775 &selection.send,
1776 &selection.have_closure,
1777 selection.peer_held.as_deref(),
1778 &opts,
1779 &selection.name_hashes,
1780 None,
1781 )
1782 .unwrap();
1783
1784 let reused = plan
1785 .entries
1786 .iter()
1787 .filter(|e| e.reused_delta_zlib.is_some())
1788 .count();
1789 let trees_and_blobs = plan
1790 .entries
1791 .iter()
1792 .filter(|e| matches!(e.kind, ObjectKind::Tree | ObjectKind::Blob))
1793 .count();
1794 assert!(
1795 reused * 2 > trees_and_blobs,
1796 "most trees and blobs keep their on-disk delta ({reused} of {trees_and_blobs})"
1797 );
1798 }
1799
1800 fn loose_history(commits: usize) -> (tempfile::TempDir, Repository, ObjectId) {
1803 let tmp = tempfile::tempdir().unwrap();
1804 let dir = tmp.path();
1805 grit_test_support::git(dir, &["init", "-q", "-b", "main"]);
1806 std::fs::create_dir_all(dir.join("src")).unwrap();
1807 for i in 0..20 {
1808 std::fs::write(
1809 dir.join(format!("src/stable{i}.txt")),
1810 format!("stable {i}\n"),
1811 )
1812 .unwrap();
1813 }
1814 let mut lines: Vec<String> = (0..200).map(|i| format!("line {i} of the file")).collect();
1815 for c in 0..commits {
1816 lines[(c * 13) % 200] = format!("changed in commit {c}");
1817 std::fs::write(dir.join("src/file.txt"), lines.join("\n")).unwrap();
1818 std::fs::write(dir.join("src/other.txt"), format!("other {c}\n")).unwrap();
1819 grit_test_support::git(dir, &["add", "."]);
1820 grit_test_support::git(
1821 dir,
1822 &[
1823 "-c",
1824 "user.name=T",
1825 "-c",
1826 "user.email=t@e",
1827 "commit",
1828 "-qm",
1829 "c",
1830 ],
1831 );
1832 }
1833 let tip = grit_test_support::git(dir, &["rev-parse", "main"]);
1834 let tip = ObjectId::from_hex(tip.trim()).unwrap();
1835 let repo = Repository::open(&dir.join(".git"), &RepositoryOptions::empty()).unwrap();
1836 (tmp, repo, tip)
1837 }
1838
1839 #[cfg_attr(
1840 not(feature = "full-tests"),
1841 ignore = "starts system git or is heavy; run with --features full-tests"
1842 )]
1843 #[test]
1844 fn loose_plan_deltifies_trees_and_blobs_with_compressed_search_output() {
1845 let (tmp, repo, tip) = loose_history(12);
1846 assert!(!objects_dir_has_local_packs(repo.odb().objects_dir()));
1847 let empty = HashSet::new();
1848 let opts = PackBuildOptions::for_local_copy(None);
1849 let selection = crate::pack_object_select::enumerate_pack_objects_naming(
1850 repo.odb(),
1851 &[tip],
1852 &[],
1853 &PackEnumerateOptions {
1854 filter: None,
1855 shallow_grafts: &empty,
1856 have_shallow_grafts: Some(&empty),
1857 use_bitmaps: false,
1858 exclude_objects: None,
1859 peer_odb: None,
1860 have_boundary: HaveBoundary::CommitClosure,
1861 },
1862 NameTrees::for_delta_search(&opts),
1863 )
1864 .unwrap();
1865 let plan = plan_deltas(
1866 repo.odb(),
1867 &selection.send,
1868 &selection.have_closure,
1869 selection.peer_held.as_deref(),
1870 &opts,
1871 &selection.name_hashes,
1872 Some(1),
1873 )
1874 .unwrap();
1875 let deltas_of = |kind| {
1876 plan.entries
1877 .iter()
1878 .filter(|e| e.kind == kind && e.base.is_some())
1879 .count()
1880 };
1881 assert!(deltas_of(ObjectKind::Blob) >= 10, "blob deltas");
1882 assert!(deltas_of(ObjectKind::Tree) >= 10, "subtree deltas");
1883 assert!(plan
1884 .entries
1885 .iter()
1886 .filter(|e| e.base.is_some())
1887 .all(|e| e.searched_delta.is_some()));
1888
1889 let pack = crate::pack_delta::serialize_pack_with_deltas(
1890 repo.odb(),
1891 &plan.entries,
1892 &opts,
1893 Some(2),
1894 )
1895 .unwrap();
1896 std::fs::write(tmp.path().join("plan.pack"), &pack.bytes).unwrap();
1897 grit_test_support::git(tmp.path(), &["index-pack", "--strict", "plan.pack"]);
1898 }
1899
1900 #[test]
1901 fn serve_options_follow_pack_config() {
1902 use crate::config::{ConfigFile, ConfigScope, ConfigSet};
1903 let file = ConfigFile::parse(
1904 Path::new(".git/config"),
1905 "[pack]\n\twindow = 4\n\tdepth = 0\n",
1906 ConfigScope::Local,
1907 )
1908 .unwrap();
1909 let mut cfg = ConfigSet::new();
1910 cfg.merge(&file);
1911 let opts = PackBuildOptions::for_serve(Some(&cfg), true, false);
1912 assert_eq!((opts.window, opts.max_depth), (4, 0));
1913 assert!(opts.thin && !opts.use_ofs_delta && opts.delta);
1914 assert!(
1915 opts.reuse_deltas,
1916 "like git upload-pack, depth 0 stops new deltas but still sends on-disk deltas"
1917 );
1918
1919 let defaults = PackBuildOptions::for_serve(None, false, true);
1920 assert_eq!(
1921 (defaults.window, defaults.max_depth),
1922 (10, crate::pack::DEFAULT_PACK_DEPTH)
1923 );
1924 assert!(defaults.reuse_deltas && defaults.reuse_objects && !defaults.thin);
1925 }
1926
1927 struct PeerHoldsSentBlob {
1931 dir: tempfile::TempDir,
1932 repo: Repository,
1933 old: ObjectId,
1934 new: ObjectId,
1935 peer_objects: Vec<ObjectId>,
1937 peer_commits: HashSet<ObjectId>,
1938 send: Vec<ObjectId>,
1939 name_hashes: HashMap<ObjectId, u32>,
1940 }
1941
1942 impl PeerHoldsSentBlob {
1943 fn new() -> Self {
1944 use crate::objects::{serialize_tree, TreeEntry};
1945 use crate::pack_name_hash::pack_name_hash;
1946 let dir = tempfile::TempDir::new().unwrap();
1947 let repo =
1948 Repository::init(dir.path(), &crate::repo::InitOptions::default()).expect("init");
1949 let odb = repo.odb();
1950 let tree = |entries: &[(&str, ObjectId)]| {
1951 let entries: Vec<TreeEntry> = entries
1952 .iter()
1953 .map(|&(name, oid)| TreeEntry {
1954 mode: 0o100644,
1955 name: name.as_bytes().to_vec(),
1956 oid,
1957 })
1958 .collect();
1959 odb.write(ObjectKind::Tree, &serialize_tree(&entries))
1960 .unwrap()
1961 };
1962 let commit = |tree: ObjectId, parents: &[ObjectId]| {
1963 let parents: String = parents
1964 .iter()
1965 .map(|p| format!("parent {}\n", p.to_hex()))
1966 .collect();
1967 let body = format!(
1968 "tree {}\n{parents}author A <a@example.com> 0 +0000\ncommitter A <a@example.com> 0 +0000\n\nc\n",
1969 tree.to_hex()
1970 );
1971 odb.write(ObjectKind::Commit, body.as_bytes()).unwrap()
1972 };
1973 let lines: Vec<String> = (0..200).map(|i| format!("line {i} of f.txt")).collect();
1974 let old = odb
1975 .write(ObjectKind::Blob, lines.join("\n").as_bytes())
1976 .unwrap();
1977 let new = odb
1978 .write(
1979 ObjectKind::Blob,
1980 format!("{}\nmore\n", lines.join("\n")).as_bytes(),
1981 )
1982 .unwrap();
1983 let old_tree = tree(&[("f.txt", old)]);
1984 let new_tree = tree(&[("f.txt", new)]);
1985 let first = commit(old_tree, &[]);
1986 let second = commit(new_tree, &[]);
1987 let extra = odb.write(ObjectKind::Blob, b"extra\n").unwrap();
1988 let merge_tree = tree(&[("extra.txt", extra), ("f.txt", new)]);
1989 let merge = commit(merge_tree, &[first, second]);
1990 Self {
1991 dir,
1992 old,
1993 new,
1994 peer_objects: vec![old, new, old_tree, new_tree, first, second],
1995 peer_commits: HashSet::from([first, second]),
1996 send: vec![merge, merge_tree, new, extra],
1997 name_hashes: HashMap::from([
1998 (new, pack_name_hash("f.txt")),
1999 (extra, pack_name_hash("extra.txt")),
2000 ]),
2001 repo,
2002 }
2003 }
2004
2005 fn thin_plan(&self) -> DeltaPlan {
2006 plan_deltas(
2007 self.repo.odb(),
2008 &self.send,
2009 &self.peer_commits,
2010 None,
2011 &PackBuildOptions::for_serve(None, true, true),
2012 &self.name_hashes,
2013 Some(1),
2014 )
2015 .unwrap()
2016 }
2017 }
2018
2019 #[test]
2020 fn thin_pack_never_deltas_a_sent_blob_against_itself() {
2021 let fx = PeerHoldsSentBlob::new();
2022 let plan = fx.thin_plan();
2023 assert_eq!(plan.entries.len(), fx.send.len());
2024 for entry in &plan.entries {
2025 assert_ne!(
2026 entry.base,
2027 Some(entry.oid),
2028 "a delta against itself never resolves"
2029 );
2030 }
2031 let new = plan.entries.iter().find(|e| e.oid == fx.new).unwrap();
2032 assert_eq!(new.base, Some(fx.old), "the other held version is the base");
2033 }
2034
2035 #[cfg_attr(
2036 not(feature = "full-tests"),
2037 ignore = "starts system git or is heavy; run with --features full-tests"
2038 )]
2039 #[test]
2040 fn thin_pack_with_held_sent_blob_completes_in_git() {
2041 let fx = PeerHoldsSentBlob::new();
2042 let plan = fx.thin_plan();
2043 let opts = PackBuildOptions::for_serve(None, true, true);
2044 let pack = crate::pack_delta::serialize_pack_with_deltas(
2045 fx.repo.odb(),
2046 &plan.entries,
2047 &opts,
2048 Some(1),
2049 )
2050 .unwrap();
2051
2052 let root = fx.dir.path();
2053 grit_test_support::git(root, &["init", "-q", "--bare", "peer"]);
2054 let peer = root.join("peer");
2055 for oid in &fx.peer_objects {
2056 let obj = fx.repo.odb().read(oid).unwrap();
2057 let written = grit_test_support::git_cmd(&[
2058 "hash-object",
2059 "-w",
2060 "--stdin",
2061 "-t",
2062 obj.kind.as_str(),
2063 ])
2064 .in_dir(&peer)
2065 .stdin(obj.data)
2066 .suc();
2067 assert_eq!(written.stdout.trim(), oid.to_hex());
2068 }
2069 grit_test_support::git_cmd(&["index-pack", "--strict", "--fix-thin", "--stdin"])
2070 .in_dir(&peer)
2071 .stdin(pack.bytes)
2072 .suc();
2073 let merge = fx.send[0].to_hex();
2074 grit_test_support::git(&peer, &["update-ref", "refs/heads/main", &merge]);
2075 grit_test_support::git(&peer, &["fsck", "--strict", "--no-dangling"]);
2076 }
2077}