1#![allow(dead_code)]
7
8use crate::environment::RepositoryOptions;
9
10use std::collections::{HashMap, HashSet, VecDeque};
11use std::io::Write;
12use std::path::Path;
13
14use flate2::write::ZlibEncoder;
15use flate2::Compression;
16
17use crate::bitmap_walk::ReachableSet;
18use crate::delta_encode::{encode_prefix_extension_delta, DeltaIndex};
19use crate::error::{Error, Result};
20use crate::hash::HashingWriter;
21use crate::objects::{
22 empty_object_id_hash_set, oid_hash_map, oid_hash_set, oid_hash_set_from, parse_commit,
23 parse_tag, HashAlgo, ObjectId, ObjectInfo, ObjectKind, OidHashMap, OidHashSet, TreeRefIter,
24};
25use crate::odb::Odb;
26use crate::pack_object_select::{
27 append_included_tags, HaveBoundary, NameTrees, PackObjectSelection, TagCandidate,
28};
29use crate::parallel::{try_par_map_force, ParallelHashError};
30use crate::progress::CancelSignal;
31use crate::repo::Repository;
32use crate::rev_list::ObjectFilter;
33
34#[derive(Clone, Copy, Debug)]
36pub struct PackBuildOptions {
37 pub thin: bool,
40 pub delta: bool,
42 pub window: usize,
44 pub max_depth: usize,
46 pub use_ofs_delta: bool,
48 pub respect_islands: bool,
50 pub reuse_deltas: bool,
52 pub reuse_objects: bool,
54 pub use_bitmaps: bool,
56 pub window_memory: Option<u64>,
61}
62
63pub const SERVE_WINDOW_MEMORY: u64 = 32 * 1024 * 1024;
67
68impl Default for PackBuildOptions {
69 fn default() -> Self {
70 Self {
71 thin: false,
72 delta: false,
73 window: 10,
74 max_depth: 50,
75 use_ofs_delta: true,
76 respect_islands: false,
77 reuse_deltas: true,
78 reuse_objects: true,
79 use_bitmaps: true,
80 window_memory: None,
81 }
82 }
83}
84
85impl PackBuildOptions {
86 #[must_use]
88 pub fn window_and_depth_from_config(cfg: Option<&crate::config::ConfigSet>) -> (usize, usize) {
89 cfg.map(|c| (c.pack_object_window(), c.pack_object_depth()))
90 .unwrap_or((10, crate::pack::DEFAULT_PACK_DEPTH))
91 }
92
93 fn window_memory_from_config(cfg: Option<&crate::config::ConfigSet>) -> u64 {
94 cfg.map(crate::config::ConfigSet::pack_window_memory_bytes)
95 .filter(|&bytes| bytes > 0)
96 .unwrap_or(SERVE_WINDOW_MEMORY)
97 }
98
99 #[must_use]
101 pub fn for_local_copy(cfg: Option<&crate::config::ConfigSet>) -> Self {
102 let (window, max_depth) = Self::window_and_depth_from_config(cfg);
103 Self {
104 thin: false,
105 delta: true,
106 window,
107 max_depth,
108 use_ofs_delta: true,
109 reuse_deltas: max_depth > 0,
110 reuse_objects: true,
111 use_bitmaps: true,
112 window_memory: Some(Self::window_memory_from_config(cfg)),
113 ..Default::default()
114 }
115 }
116
117 #[must_use]
120 pub fn for_repack_rewrite(cfg: Option<&crate::config::ConfigSet>) -> Self {
121 let max_depth = Self::window_and_depth_from_config(cfg).1;
122 Self {
123 thin: false,
124 delta: true,
125 window: 0,
126 max_depth,
127 use_ofs_delta: true,
128 reuse_deltas: max_depth > 0,
129 reuse_objects: true,
130 use_bitmaps: true,
131 ..Default::default()
132 }
133 }
134
135 #[must_use]
150 pub fn for_serve(
151 cfg: Option<&crate::config::ConfigSet>,
152 thin: bool,
153 use_ofs_delta: bool,
154 ) -> Self {
155 let (window, max_depth) = Self::window_and_depth_from_config(cfg);
156 let window_memory = cfg
157 .map(crate::config::ConfigSet::pack_window_memory_bytes)
158 .filter(|&bytes| bytes > 0)
159 .unwrap_or(SERVE_WINDOW_MEMORY);
160 Self {
161 thin,
162 delta: true,
163 window,
164 max_depth,
165 use_ofs_delta,
166 reuse_deltas: true,
167 reuse_objects: true,
168 use_bitmaps: true,
169 window_memory: Some(window_memory),
170 ..Default::default()
171 }
172 }
173
174 #[must_use]
179 pub fn for_local_push(cfg: Option<&crate::config::ConfigSet>) -> Self {
180 let (window, max_depth) = Self::window_and_depth_from_config(cfg);
181 Self {
182 thin: true,
183 delta: true,
184 window,
185 max_depth,
186 use_ofs_delta: true,
187 reuse_deltas: true,
188 reuse_objects: true,
189 use_bitmaps: true,
190 window_memory: Some(Self::window_memory_from_config(cfg)),
191 ..Default::default()
192 }
193 }
194}
195
196#[derive(Clone, Copy, Debug, Default)]
198pub struct PackObjectsOptions {
199 pub build: PackBuildOptions,
201 pub search_threads: Option<usize>,
203 pub write_threads: Option<usize>,
205}
206
207impl From<PackBuildOptions> for PackObjectsOptions {
208 fn from(build: PackBuildOptions) -> Self {
209 Self {
210 build,
211 search_threads: None,
212 write_threads: None,
213 }
214 }
215}
216
217#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
219pub struct PackStats {
220 pub object_count: u32,
222 pub pack_bytes: u64,
224 pub delta_count: u32,
226 pub verbatim_reuse_bytes: u64,
228 pub verbatim_reuse_objects: u32,
230 pub pack_ofs_fixup_objects: u32,
232 pub pack_ref_from_ofs_objects: u32,
234}
235
236#[derive(Clone, Copy, Debug, PartialEq, Eq)]
238pub enum PackProgress {
239 Enumerated {
241 total: usize,
243 },
244 PlannedDeltas,
246 WroteObject {
248 written: usize,
250 total: usize,
252 },
253}
254
255pub(crate) struct PackBuildProgress<'a> {
257 sink: &'a mut dyn crate::progress::ProgressSink,
258 copying_started: bool,
259 write_total: usize,
260 written: usize,
261}
262
263impl<'a> PackBuildProgress<'a> {
264 pub fn new(sink: &'a mut dyn crate::progress::ProgressSink) -> Self {
266 Self {
267 sink,
268 copying_started: false,
269 write_total: 0,
270 written: 0,
271 }
272 }
273
274 pub fn report(&mut self, ev: PackProgress) {
276 use crate::progress::ProgressPhase;
277 match ev {
278 PackProgress::Enumerated { total } => {
279 self.write_total = total;
280 self.sink
281 .start(ProgressPhase::EnumeratingObjects, Some(total as u64));
282 self.sink.set(total as u64);
283 self.sink.finish();
284 }
285 PackProgress::PlannedDeltas => {
286 self.sink.start(ProgressPhase::CopyingObjects, None);
287 self.copying_started = true;
288 }
289 PackProgress::WroteObject { written, total } => {
290 if !self.copying_started {
291 self.sink
292 .start(ProgressPhase::CopyingObjects, Some(total as u64));
293 self.copying_started = true;
294 }
295 self.written = written;
296 self.sink.set(written as u64);
297 }
298 }
299 }
300
301 pub fn finish_copying(&mut self) {
303 if self.copying_started {
304 self.sink.finish();
305 self.copying_started = false;
306 }
307 }
308}
309
310pub struct PackObjects<'a> {
312 odb: &'a Odb,
313 source_odb: &'a Odb,
314 opts: PackObjectsOptions,
315 wants: Vec<ObjectId>,
316 haves: Vec<ObjectId>,
317 have_shallow: OidHashSet,
318 source_shallow: OidHashSet,
319 progress: Option<Box<dyn FnMut(PackProgress)>>,
320}
321
322impl<'a> PackObjects<'a> {
323 #[must_use]
325 pub fn new(odb: &'a Odb, opts: PackObjectsOptions) -> Self {
326 Self {
327 odb,
328 source_odb: odb,
329 opts,
330 wants: Vec::new(),
331 haves: Vec::new(),
332 have_shallow: oid_hash_set(),
333 source_shallow: oid_hash_set(),
334 progress: None,
335 }
336 }
337
338 #[must_use]
340 pub fn with_source_odb(mut self, source: &'a Odb) -> Self {
341 self.source_odb = source;
342 self
343 }
344
345 #[must_use]
347 pub fn wants(mut self, wants: &[ObjectId]) -> Self {
348 self.wants.extend_from_slice(wants);
349 self
350 }
351
352 #[must_use]
354 pub fn haves(mut self, haves: &[ObjectId]) -> Self {
355 self.haves.extend_from_slice(haves);
356 self
357 }
358
359 #[must_use]
361 pub fn have_shallow_grafts(mut self, grafts: &HashSet<ObjectId>) -> Self {
362 self.have_shallow = grafts.iter().copied().collect();
363 self
364 }
365
366 #[must_use]
368 pub fn source_shallow_grafts(mut self, grafts: &HashSet<ObjectId>) -> Self {
369 self.source_shallow = grafts.iter().copied().collect();
370 self
371 }
372
373 #[must_use]
375 pub fn progress<F>(mut self, f: F) -> Self
376 where
377 F: FnMut(PackProgress) + 'static,
378 {
379 self.progress = Some(Box::new(f));
380 self
381 }
382
383 fn fire(&mut self, ev: PackProgress) {
384 if let Some(ref mut f) = self.progress {
385 f(ev);
386 }
387 }
388
389 pub fn write_to(self, w: &mut dyn Write) -> Result<PackStats> {
395 self.write_to_with_cancel(w, &CancelSignal::never())
396 }
397
398 pub(crate) fn write_to_with_cancel(
400 mut self,
401 w: &mut dyn Write,
402 stop: &CancelSignal<'_>,
403 ) -> Result<PackStats> {
404 let opts = self.opts.build;
405 let have_shallow: HashSet<ObjectId> = self.have_shallow.iter().copied().collect();
406 let source_shallow: HashSet<ObjectId> = self.source_shallow.iter().copied().collect();
407 let peer_odb = (!std::ptr::eq(self.source_odb, self.odb)).then_some(self.odb);
408 let selection = crate::pack_object_select::enumerate_pack_objects_naming(
409 self.source_odb,
410 &self.wants,
411 &self.haves,
412 &crate::pack_object_select::PackEnumerateOptions {
413 filter: None,
414 shallow_grafts: &source_shallow,
415 have_shallow_grafts: Some(&have_shallow),
416 use_bitmaps: opts.use_bitmaps,
417 exclude_objects: None,
418 peer_odb,
419 have_boundary: HaveBoundary::CommitClosure,
420 },
421 NameTrees::for_delta_search(&opts),
422 false,
423 stop,
424 )?;
425 let send = selection.send;
426 self.fire(PackProgress::Enumerated { total: send.len() });
427 if !opts.delta {
428 let bytes = serialize_pack(self.source_odb, &send, &opts)?;
429 stop.poll()?;
430 w.write_all(&bytes).map_err(Error::Io)?;
431 let count = u32::from_be_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
432 return Ok(PackStats {
433 object_count: count,
434 pack_bytes: bytes.len() as u64,
435 ..Default::default()
436 });
437 }
438 let plan = plan_deltas(
439 self.source_odb,
440 &send,
441 &selection.have_closure,
442 selection.peer_held.as_deref(),
443 &opts,
444 &selection.name_hashes,
445 self.opts.search_threads,
446 stop,
447 )?;
448 self.fire(PackProgress::PlannedDeltas);
449 crate::pack_delta::write_pack_with_deltas(
450 self.source_odb,
451 &plan.entries,
452 &opts,
453 self.opts.write_threads,
454 w,
455 None,
456 stop,
457 None,
458 )
459 }
460}
461
462pub fn reachable_closure(
465 odb: &Odb,
466 roots: &[ObjectId],
467 stop: &HashSet<ObjectId>,
468 skip_missing: bool,
469 shallow_grafts: &HashSet<ObjectId>,
470) -> Result<HashSet<ObjectId>> {
471 let stop = oid_hash_set_from(stop);
472 let shallow_grafts = oid_hash_set_from(shallow_grafts);
473 let mut seen = oid_hash_set();
474 let order = collect_reachable_excluding(odb, roots, &stop, skip_missing, &shallow_grafts)?;
475 for oid in order {
476 seen.insert(oid);
477 }
478 Ok(seen.into_iter().collect())
479}
480
481pub fn build_pack(
484 odb: &Odb,
485 wants: &[ObjectId],
486 haves: &[ObjectId],
487 opts: &PackBuildOptions,
488) -> Result<Vec<u8>> {
489 build_pack_with_shallow_filter_and_boundary(
490 odb,
491 wants,
492 haves,
493 empty_object_id_hash_set(),
494 empty_object_id_hash_set(),
495 None,
496 opts,
497 HaveBoundary::CommitClosure,
498 )
499}
500
501pub fn build_pack_for_push(
511 odb: &Odb,
512 wants: &[ObjectId],
513 haves: &[ObjectId],
514 opts: &PackBuildOptions,
515) -> Result<Vec<u8>> {
516 let mut pack = Vec::new();
517 write_pack_for_push(odb, wants, haves, opts, &mut pack)?;
518 Ok(pack)
519}
520
521pub fn write_pack_for_push(
544 odb: &Odb,
545 wants: &[ObjectId],
546 haves: &[ObjectId],
547 opts: &PackBuildOptions,
548 w: &mut dyn Write,
549) -> Result<PackStats> {
550 write_pack_for_push_with_cancel(
551 odb,
552 wants,
553 haves,
554 &HashSet::new(),
555 &HashSet::new(),
556 opts,
557 w,
558 &CancelSignal::never(),
559 None,
560 )
561}
562
563#[allow(clippy::too_many_arguments)]
565pub(crate) fn write_pack_for_push_with_cancel(
566 odb: &Odb,
567 wants: &[ObjectId],
568 haves: &[ObjectId],
569 have_shallow: &HashSet<ObjectId>,
570 source_shallow: &HashSet<ObjectId>,
571 opts: &PackBuildOptions,
572 w: &mut dyn Write,
573 stop: &CancelSignal<'_>,
574 progress: Option<&mut dyn crate::progress::ProgressSink>,
575) -> Result<PackStats> {
576 odb.with_pack_read_context(|| {
579 write_pack_with_shallow_and_filter(
580 odb,
581 wants,
582 haves,
583 have_shallow,
584 source_shallow,
585 None,
586 opts,
587 HaveBoundary::TipsOnly,
588 &[],
589 w,
590 stop,
591 progress,
592 )
593 })
594}
595
596pub fn build_pack_with_shallow_and_filter(
601 odb: &Odb,
602 wants: &[ObjectId],
603 haves: &[ObjectId],
604 have_shallow: &HashSet<ObjectId>,
605 source_shallow: &HashSet<ObjectId>,
606 filter: Option<&ObjectFilter>,
607 opts: &PackBuildOptions,
608) -> Result<Vec<u8>> {
609 build_pack_with_shallow_filter_and_boundary(
610 odb,
611 wants,
612 haves,
613 have_shallow,
614 source_shallow,
615 filter,
616 opts,
617 HaveBoundary::CommitClosure,
618 )
619}
620
621#[allow(clippy::too_many_arguments)]
622fn build_pack_with_shallow_filter_and_boundary(
623 odb: &Odb,
624 wants: &[ObjectId],
625 haves: &[ObjectId],
626 have_shallow: &HashSet<ObjectId>,
627 source_shallow: &HashSet<ObjectId>,
628 filter: Option<&ObjectFilter>,
629 opts: &PackBuildOptions,
630 have_boundary: HaveBoundary,
631) -> Result<Vec<u8>> {
632 let mut pack = Vec::new();
633 write_pack_with_shallow_and_filter(
634 odb,
635 wants,
636 haves,
637 have_shallow,
638 source_shallow,
639 filter,
640 opts,
641 have_boundary,
642 &[],
643 &mut pack,
644 &CancelSignal::never(),
645 None,
646 )?;
647 Ok(pack)
648}
649
650#[allow(clippy::too_many_arguments)]
653fn write_pack_with_shallow_and_filter(
654 odb: &Odb,
655 wants: &[ObjectId],
656 haves: &[ObjectId],
657 have_shallow: &HashSet<ObjectId>,
658 source_shallow: &HashSet<ObjectId>,
659 filter: Option<&ObjectFilter>,
660 opts: &PackBuildOptions,
661 have_boundary: HaveBoundary,
662 include_tags: &[TagCandidate],
663 w: &mut dyn Write,
664 stop: &CancelSignal<'_>,
665 progress: Option<&mut dyn crate::progress::ProgressSink>,
666) -> Result<PackStats> {
667 let mut selection = crate::pack_object_select::enumerate_pack_objects_naming(
668 odb,
669 wants,
670 haves,
671 &crate::pack_object_select::PackEnumerateOptions {
672 filter,
673 shallow_grafts: source_shallow,
674 have_shallow_grafts: Some(have_shallow),
675 use_bitmaps: opts.use_bitmaps,
676 exclude_objects: None,
677 peer_odb: None,
678 have_boundary,
679 },
680 NameTrees::for_delta_search(opts),
681 false,
682 stop,
683 )?;
684 append_included_tags(odb, &mut selection.send, include_tags)?;
685 write_selected_pack(odb, &selection, opts, w, stop, progress)
686}
687
688fn write_selected_pack(
691 odb: &Odb,
692 selection: &PackObjectSelection,
693 opts: &PackBuildOptions,
694 w: &mut dyn Write,
695 stop: &CancelSignal<'_>,
696 progress: Option<&mut dyn crate::progress::ProgressSink>,
697) -> Result<PackStats> {
698 let mut reporter = progress.map(PackBuildProgress::new);
699 if let Some(r) = reporter.as_mut() {
700 r.report(PackProgress::Enumerated {
701 total: selection.send.len(),
702 });
703 }
704 if !opts.delta {
705 let bytes = serialize_pack(odb, &selection.send, opts)?;
706 stop.poll()?;
707 w.write_all(&bytes).map_err(Error::Io)?;
708 if let Some(r) = reporter.as_mut() {
709 r.report(PackProgress::PlannedDeltas);
710 r.report(PackProgress::WroteObject {
711 written: selection.send.len(),
712 total: selection.send.len(),
713 });
714 r.finish_copying();
715 }
716 return Ok(PackStats {
717 object_count: u32::try_from(selection.send.len()).unwrap_or(u32::MAX),
718 pack_bytes: bytes.len() as u64,
719 ..PackStats::default()
720 });
721 }
722 let plan = plan_deltas(
723 odb,
724 &selection.send,
725 &selection.have_closure,
726 selection.peer_held.as_deref(),
727 opts,
728 &selection.name_hashes,
729 None,
730 stop,
731 )?;
732 if let Some(r) = reporter.as_mut() {
733 r.report(PackProgress::PlannedDeltas);
734 }
735 let total = selection.send.len();
736 let stats = if reporter.is_some() {
737 let mut on_objects = |batch: u64| {
738 if let Some(r) = reporter.as_mut() {
739 r.written = r.written.saturating_add(batch as usize);
740 r.report(PackProgress::WroteObject {
741 written: r.written,
742 total,
743 });
744 }
745 };
746 crate::pack_delta::write_pack_with_deltas(
747 odb,
748 &plan.entries,
749 opts,
750 None,
751 w,
752 None,
753 stop,
754 Some(&mut on_objects),
755 )?
756 } else {
757 crate::pack_delta::write_pack_with_deltas(
758 odb,
759 &plan.entries,
760 opts,
761 None,
762 w,
763 None,
764 stop,
765 None,
766 )?
767 };
768 if let Some(r) = reporter.as_mut() {
769 r.finish_copying();
770 }
771 Ok(stats)
772}
773
774#[allow(clippy::too_many_arguments)]
783pub fn write_upload_pack_stream(
784 repo: &Repository,
785 wants: &[ObjectId],
786 haves: &[ObjectId],
787 source_shallow: &HashSet<ObjectId>,
788 have_shallow: &HashSet<ObjectId>,
789 filter: Option<&ObjectFilter>,
790 opts: &PackBuildOptions,
791 progress: &mut dyn FnMut(PackProgress),
792 w: &mut dyn Write,
793) -> Result<PackStats> {
794 write_upload_pack_stream_with_cancel(
795 repo,
796 wants,
797 haves,
798 source_shallow,
799 have_shallow,
800 filter,
801 &[],
802 opts,
803 progress,
804 w,
805 &CancelSignal::never(),
806 )
807}
808
809#[allow(clippy::too_many_arguments)]
813pub(crate) fn write_upload_pack_stream_with_cancel(
814 repo: &Repository,
815 wants: &[ObjectId],
816 haves: &[ObjectId],
817 source_shallow: &HashSet<ObjectId>,
818 have_shallow: &HashSet<ObjectId>,
819 filter: Option<&ObjectFilter>,
820 include_tags: &[TagCandidate],
821 opts: &PackBuildOptions,
822 progress: &mut dyn FnMut(PackProgress),
823 w: &mut dyn Write,
824 stop: &CancelSignal<'_>,
825) -> Result<PackStats> {
826 repo.odb().with_pack_read_context(|| {
827 write_upload_pack_stream_in_context(
828 repo,
829 wants,
830 haves,
831 source_shallow,
832 have_shallow,
833 filter,
834 include_tags,
835 opts,
836 progress,
837 w,
838 stop,
839 )
840 })
841}
842
843#[allow(clippy::too_many_arguments)]
844fn write_upload_pack_stream_in_context(
845 repo: &Repository,
846 wants: &[ObjectId],
847 haves: &[ObjectId],
848 source_shallow: &HashSet<ObjectId>,
849 have_shallow: &HashSet<ObjectId>,
850 filter: Option<&ObjectFilter>,
851 include_tags: &[TagCandidate],
852 opts: &PackBuildOptions,
853 progress: &mut dyn FnMut(PackProgress),
854 w: &mut dyn Write,
855 stop: &CancelSignal<'_>,
856) -> Result<PackStats> {
857 if opts.use_bitmaps && filter.is_none() {
858 if let Some(stats) = crate::pack_reuse::try_write_streaming_pack_including_tags(
859 repo,
860 wants,
861 haves,
862 source_shallow,
863 have_shallow,
864 filter,
865 include_tags,
866 opts,
867 progress,
868 w,
869 stop,
870 )? {
871 return Ok(stats);
872 }
873 }
874 write_pack_with_shallow_and_filter(
877 repo.odb(),
878 wants,
879 haves,
880 have_shallow,
881 source_shallow,
882 filter,
883 opts,
884 HaveBoundary::TipsOnly,
885 include_tags,
886 w,
887 stop,
888 None,
889 )
890}
891
892type PackIndexRows = Vec<(ObjectId, u64, u32)>;
893
894#[allow(clippy::too_many_arguments)]
900pub(crate) fn select_upload_pack_objects_probing(
901 repo: &Repository,
902 wants: &[ObjectId],
903 haves: &[ObjectId],
904 source_shallow: &HashSet<ObjectId>,
905 have_shallow: &HashSet<ObjectId>,
906 filter: Option<&ObjectFilter>,
907 opts: &PackBuildOptions,
908) -> Result<(
909 PackObjectSelection,
910 Vec<crate::pack_object_select::MissingBlob>,
911)> {
912 repo.odb().with_pack_read_context(|| {
913 crate::pack_object_select::enumerate_pack_objects_probing(
914 repo.odb(),
915 wants,
916 haves,
917 &crate::pack_object_select::PackEnumerateOptions {
918 filter,
919 shallow_grafts: source_shallow,
920 have_shallow_grafts: Some(have_shallow),
921 use_bitmaps: false,
922 exclude_objects: None,
923 peer_odb: None,
924 have_boundary: HaveBoundary::TipsOnly,
925 },
926 NameTrees::for_delta_search(opts),
927 &CancelSignal::never(),
928 )
929 })
930}
931
932pub(crate) fn write_upload_pack_selection(
935 repo: &Repository,
936 selection: &PackObjectSelection,
937 opts: &PackBuildOptions,
938 w: &mut dyn Write,
939 stop: &CancelSignal<'_>,
940) -> Result<PackStats> {
941 repo.odb()
942 .with_pack_read_context(|| write_selected_pack(repo.odb(), selection, opts, w, stop, None))
943}
944
945fn write_pack_header(w: &mut dyn Write, count: u32) -> Result<()> {
946 w.write_all(b"PACK").map_err(Error::Io)?;
947 w.write_all(&2u32.to_be_bytes()).map_err(Error::Io)?;
948 w.write_all(&count.to_be_bytes()).map_err(Error::Io)?;
949 Ok(())
950}
951
952pub(crate) fn write_delta_pack_stream(
954 odb: &Odb,
955 plan: &DeltaPlan,
956 opts: &PackBuildOptions,
957 w: &mut dyn Write,
958 reuse: Option<&mut crate::pack_reuse::StreamingReuse>,
959) -> Result<PackStats> {
960 let algo = odb.hash_algo();
961 let mut out = HashingWriter::new(algo, w);
962 let reuse_stats = if let Some(reuse) = reuse {
963 let (_, body_bytes) = write_delta_plan_stream_body(&mut out, odb, plan, opts, Some(reuse))?;
964 out.finish().map_err(Error::Io)?;
965 let pack_bytes = 12u64 + body_bytes + algo.len() as u64;
966 let stats = reuse.take_stats();
967 (pack_bytes, stats)
968 } else {
969 let (_, body_bytes) = write_delta_plan_stream_body(&mut out, odb, plan, opts, None)?;
970 out.finish().map_err(Error::Io)?;
971 (
972 12u64 + body_bytes + algo.len() as u64,
973 crate::pack_reuse::ReuseWriteStats::default(),
974 )
975 };
976 let delta_count = plan.entries.iter().filter(|e| e.base.is_some()).count() as u32;
977 Ok(PackStats {
978 object_count: u32::try_from(plan.entries.len())
979 .map_err(|_| Error::CorruptObject("pack object count exceeds u32".to_owned()))?,
980 pack_bytes: reuse_stats.0,
981 delta_count,
982 verbatim_reuse_bytes: reuse_stats.1.verbatim_reuse_bytes,
983 verbatim_reuse_objects: reuse_stats.1.verbatim_reuse_objects,
984 pack_ofs_fixup_objects: reuse_stats.1.pack_ofs_fixup_objects,
985 pack_ref_from_ofs_objects: reuse_stats.1.pack_ref_from_ofs_objects,
986 })
987}
988
989fn write_delta_plan_stream_body(
990 w: &mut dyn Write,
991 odb: &Odb,
992 plan: &DeltaPlan,
993 opts: &PackBuildOptions,
994 reuse: Option<&mut crate::pack_reuse::StreamingReuse>,
995) -> Result<(PackIndexRows, u64)> {
996 let count = u32::try_from(plan.entries.len())
997 .map_err(|_| Error::CorruptObject("pack object count exceeds u32".to_owned()))?;
998 write_pack_header(w, count)?;
999 let mut index_rows = Vec::with_capacity(plan.entries.len());
1000 let body_bytes = write_planned_entries(w, odb, plan, opts, 12, reuse, &mut index_rows)?;
1001 Ok((index_rows, body_bytes))
1002}
1003
1004pub(crate) const PACK_STREAM_COPY_CHUNK: usize = 64 * 1024;
1005
1006pub(crate) struct CrcWriter<'a, W: Write + ?Sized> {
1007 inner: &'a mut W,
1008 crc: crc32fast::Hasher,
1009}
1010
1011impl<'a, W: Write + ?Sized> CrcWriter<'a, W> {
1012 pub fn new(inner: &'a mut W, prefix: &[u8]) -> Self {
1013 let mut crc = crc32fast::Hasher::new();
1014 crc.update(prefix);
1015 Self { inner, crc }
1016 }
1017
1018 pub(crate) fn finalize(self) -> u32 {
1019 self.crc.finalize()
1020 }
1021}
1022
1023impl<W: Write + ?Sized> Write for CrcWriter<'_, W> {
1024 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1025 let n = self.inner.write(buf)?;
1026 self.crc.update(&buf[..n]);
1027 Ok(n)
1028 }
1029
1030 fn flush(&mut self) -> std::io::Result<()> {
1031 self.inner.flush()
1032 }
1033}
1034
1035struct ByteCounter<'a, W: Write + ?Sized> {
1036 inner: &'a mut W,
1037 n: u64,
1038}
1039
1040impl<W: Write + ?Sized> Write for ByteCounter<'_, W> {
1041 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1042 let n = self.inner.write(buf)?;
1043 self.n += n as u64;
1044 Ok(n)
1045 }
1046
1047 fn flush(&mut self) -> std::io::Result<()> {
1048 self.inner.flush()
1049 }
1050}
1051
1052pub(crate) fn write_all_chunked(w: &mut dyn Write, data: &[u8]) -> Result<()> {
1053 for chunk in data.chunks(PACK_STREAM_COPY_CHUNK) {
1054 w.write_all(chunk).map_err(Error::Io)?;
1055 }
1056 Ok(())
1057}
1058
1059fn write_planned_entries(
1060 w: &mut dyn Write,
1061 odb: &Odb,
1062 plan: &DeltaPlan,
1063 opts: &PackBuildOptions,
1064 pack_body_start: u64,
1065 mut reuse: Option<&mut crate::pack_reuse::StreamingReuse>,
1066 index_rows: &mut Vec<(ObjectId, u64, u32)>,
1067) -> Result<u64> {
1068 let algo = odb.hash_algo();
1069 let compression = odb.pack_compression()?;
1070 let mut oid_to_offset: OidHashMap<u64> = oid_hash_map();
1071 let mut body_written = 0u64;
1072
1073 for entry in &plan.entries {
1074 let start = pack_body_start + body_written;
1075 let mut chunk = Vec::new();
1076 if entry.base.is_none() {
1077 let mut crc_w = CrcWriter {
1078 inner: w,
1079 crc: crc32fast::Hasher::new(),
1080 };
1081 let wrote = {
1082 let mut counter = ByteCounter {
1083 inner: &mut crc_w,
1084 n: 0,
1085 };
1086 if let Some(reuse) = reuse.as_mut() {
1087 if let Some(n) = reuse.try_write_whole(&mut counter, &entry.oid, opts)? {
1088 n
1089 } else {
1090 write_whole_pack_object_to_odb(
1091 &mut counter,
1092 odb,
1093 entry.oid,
1094 entry.kind,
1095 opts,
1096 &CancelSignal::never(),
1097 )?
1098 }
1099 } else {
1100 write_whole_pack_object_to_odb(
1101 &mut counter,
1102 odb,
1103 entry.oid,
1104 entry.kind,
1105 opts,
1106 &CancelSignal::never(),
1107 )?
1108 }
1109 };
1110 let crc = crc_w.finalize();
1111 index_rows.push((entry.oid, start, crc));
1112 body_written += wrote;
1113 oid_to_offset.insert(entry.oid, start);
1114 continue;
1115 }
1116 let Some(base_oid) = entry.base else {
1117 continue;
1118 };
1119 let in_pack_offset = oid_to_offset.get(&base_oid).copied();
1120 if let Some((delta_size, pack_bytes, zlib_start, zlib_end)) = &entry.reused_delta_zlib {
1121 let delta_len = usize::try_from(*delta_size)
1122 .map_err(|_| Error::CorruptObject("reused delta size exceeds usize".to_owned()))?;
1123 if let Some(base_off) = in_pack_offset.filter(|_| opts.use_ofs_delta) {
1124 let dist = start.checked_sub(base_off).ok_or_else(|| {
1125 Error::CorruptObject("ofs-delta distance underflow".to_owned())
1126 })?;
1127 encode_pack_object_header(&mut chunk, 6, delta_len);
1128 encode_ofs_delta_distance(&mut chunk, dist);
1129 if let Some(reuse) = reuse.as_mut() {
1130 reuse.record_ofs_fixup();
1131 }
1132 } else {
1133 encode_pack_object_header(&mut chunk, 7, delta_len);
1134 chunk.extend_from_slice(base_oid.as_bytes());
1135 }
1136 chunk.extend_from_slice(&pack_bytes[*zlib_start..*zlib_end]);
1137 if let Some(reuse) = reuse.as_mut() {
1138 reuse.record_verbatim_zlib((zlib_end - zlib_start) as u64);
1139 }
1140 } else {
1141 let computed;
1142 let (delta_len, zlib): (usize, &[u8]) = match &entry.searched_delta {
1143 Some((len, zlib)) => (*len, zlib),
1144 None => {
1145 let target_data = odb.read(&entry.oid)?.data;
1146 let base_data = odb.read(&base_oid)?.data;
1147 let delta = if target_data.starts_with(&base_data)
1148 && target_data.len() > base_data.len()
1149 {
1150 encode_prefix_extension_delta(&base_data, &target_data)?
1151 } else {
1152 DeltaIndex::new(&base_data)
1153 .encode(&target_data, 0)
1154 .ok_or_else(|| {
1155 Error::CorruptObject(
1156 "failed to encode pack delta for object".into(),
1157 )
1158 })?
1159 };
1160 let mut buf = Vec::new();
1161 write_zlib(&mut buf, &delta, compression)?;
1162 computed = buf;
1163 (delta.len(), &computed)
1164 }
1165 };
1166 if let Some(base_off) = in_pack_offset.filter(|_| opts.use_ofs_delta) {
1167 let dist = start.checked_sub(base_off).ok_or_else(|| {
1168 Error::CorruptObject("ofs-delta distance underflow".to_owned())
1169 })?;
1170 encode_pack_object_header(&mut chunk, 6, delta_len);
1171 encode_ofs_delta_distance(&mut chunk, dist);
1172 } else {
1173 encode_pack_object_header(&mut chunk, 7, delta_len);
1174 if base_oid.as_bytes().len() != algo.len() {
1175 return Err(Error::CorruptObject(
1176 "ref-delta base oid width mismatch".to_owned(),
1177 ));
1178 }
1179 chunk.extend_from_slice(base_oid.as_bytes());
1180 }
1181 chunk.extend_from_slice(zlib);
1182 }
1183 w.write_all(&chunk).map_err(Error::Io)?;
1184 index_rows.push((entry.oid, start, crc32fast::hash(&chunk)));
1185 body_written += chunk.len() as u64;
1186 oid_to_offset.insert(entry.oid, start);
1187 }
1188 Ok(body_written)
1189}
1190
1191pub fn build_pack_from_send_list(
1197 odb: &Odb,
1198 send: &[ObjectId],
1199 have_closure: &HashSet<ObjectId>,
1200 opts: &PackBuildOptions,
1201 blob_name_hashes: &HashMap<ObjectId, u32>,
1202) -> Result<Vec<u8>> {
1203 build_pack_from_send_list_with_parallel_threads(
1204 odb,
1205 send,
1206 have_closure,
1207 opts,
1208 blob_name_hashes,
1209 None,
1210 None,
1211 )
1212}
1213
1214pub fn build_pack_with_write_threads(
1218 odb: &Odb,
1219 wants: &[ObjectId],
1220 haves: &[ObjectId],
1221 opts: &PackBuildOptions,
1222 write_threads: Option<usize>,
1223) -> Result<Vec<u8>> {
1224 build_pack_with_write_threads_full(odb, wants, haves, opts, write_threads)
1225 .map(|pack| pack.bytes)
1226}
1227
1228pub(crate) fn build_pack_with_write_threads_full(
1230 odb: &Odb,
1231 wants: &[ObjectId],
1232 haves: &[ObjectId],
1233 opts: &PackBuildOptions,
1234 write_threads: Option<usize>,
1235) -> Result<SerializedDeltaPack> {
1236 let empty_shallow = empty_object_id_hash_set();
1237 let have_closure = reachable_closure(odb, haves, empty_shallow, true, empty_shallow)?;
1238 let selection = crate::pack_object_select::enumerate_pack_objects_naming(
1239 odb,
1240 wants,
1241 haves,
1242 &crate::pack_object_select::PackEnumerateOptions {
1243 filter: None,
1244 shallow_grafts: empty_shallow,
1245 have_shallow_grafts: Some(empty_shallow),
1246 use_bitmaps: opts.use_bitmaps,
1247 exclude_objects: Some(&have_closure),
1248 peer_odb: None,
1249 have_boundary: HaveBoundary::CommitClosure,
1250 },
1251 NameTrees::for_delta_search(opts),
1252 false,
1253 &CancelSignal::never(),
1254 )?;
1255 build_pack_from_send_list_full(
1256 odb,
1257 &selection.send,
1258 &selection.have_closure,
1259 opts,
1260 &selection.name_hashes,
1261 None,
1262 write_threads,
1263 )
1264}
1265
1266pub fn build_pack_with_parallel_threads(
1268 odb: &Odb,
1269 wants: &[ObjectId],
1270 haves: &[ObjectId],
1271 opts: &PackBuildOptions,
1272 search_threads: Option<usize>,
1273 write_threads: Option<usize>,
1274) -> Result<Vec<u8>> {
1275 let empty_shallow = empty_object_id_hash_set();
1276 let have_closure = reachable_closure(odb, haves, empty_shallow, true, empty_shallow)?;
1277 let selection = crate::pack_object_select::enumerate_pack_objects_naming(
1278 odb,
1279 wants,
1280 haves,
1281 &crate::pack_object_select::PackEnumerateOptions {
1282 filter: None,
1283 shallow_grafts: empty_shallow,
1284 have_shallow_grafts: Some(empty_shallow),
1285 use_bitmaps: opts.use_bitmaps,
1286 exclude_objects: Some(&have_closure),
1287 peer_odb: None,
1288 have_boundary: HaveBoundary::CommitClosure,
1289 },
1290 NameTrees::for_delta_search(opts),
1291 false,
1292 &CancelSignal::never(),
1293 )?;
1294 build_pack_from_send_list_with_parallel_threads(
1295 odb,
1296 &selection.send,
1297 &selection.have_closure,
1298 opts,
1299 &selection.name_hashes,
1300 search_threads,
1301 write_threads,
1302 )
1303}
1304
1305pub fn build_pack_from_send_list_with_write_threads(
1307 odb: &Odb,
1308 send: &[ObjectId],
1309 have_closure: &HashSet<ObjectId>,
1310 opts: &PackBuildOptions,
1311 blob_name_hashes: &HashMap<ObjectId, u32>,
1312 write_threads: Option<usize>,
1313) -> Result<Vec<u8>> {
1314 build_pack_from_send_list_with_parallel_threads(
1315 odb,
1316 send,
1317 have_closure,
1318 opts,
1319 blob_name_hashes,
1320 None,
1321 write_threads,
1322 )
1323}
1324
1325pub fn build_pack_from_send_list_with_parallel_threads(
1327 odb: &Odb,
1328 send: &[ObjectId],
1329 have_closure: &HashSet<ObjectId>,
1330 opts: &PackBuildOptions,
1331 blob_name_hashes: &HashMap<ObjectId, u32>,
1332 search_threads: Option<usize>,
1333 write_threads: Option<usize>,
1334) -> Result<Vec<u8>> {
1335 build_pack_from_send_list_full(
1336 odb,
1337 send,
1338 have_closure,
1339 opts,
1340 blob_name_hashes,
1341 search_threads,
1342 write_threads,
1343 )
1344 .map(|pack| pack.bytes)
1345}
1346
1347pub(crate) fn build_pack_from_send_list_full(
1348 odb: &Odb,
1349 send: &[ObjectId],
1350 have_closure: &HashSet<ObjectId>,
1351 opts: &PackBuildOptions,
1352 blob_name_hashes: &HashMap<ObjectId, u32>,
1353 search_threads: Option<usize>,
1354 write_threads: Option<usize>,
1355) -> Result<SerializedDeltaPack> {
1356 if !opts.delta {
1357 let bytes = serialize_pack(odb, send, opts)?;
1358 return Ok(SerializedDeltaPack {
1359 bytes,
1360 index_rows: Vec::new(),
1361 staging_path: None,
1362 });
1363 }
1364 let plan = plan_deltas(
1365 odb,
1366 send,
1367 have_closure,
1368 None,
1369 opts,
1370 blob_name_hashes,
1371 search_threads,
1372 &CancelSignal::never(),
1373 )?;
1374 crate::pack_delta::serialize_pack_with_deltas(odb, &plan.entries, opts, write_threads)
1375}
1376
1377pub(crate) fn write_pack_from_send_list(
1383 odb: &Odb,
1384 send: &[ObjectId],
1385 opts: &PackBuildOptions,
1386 name_hashes: &HashMap<ObjectId, u32>,
1387 stop: &CancelSignal<'_>,
1388 w: &mut dyn Write,
1389 mut on_objects: Option<&mut dyn FnMut(u64)>,
1390) -> Result<(PackStats, PackIndexRows)> {
1391 if !opts.delta {
1392 let bytes = serialize_pack(odb, send, opts)?;
1393 stop.poll()?;
1394 w.write_all(&bytes).map_err(Error::Io)?;
1395 if let Some(on) = on_objects.as_deref_mut() {
1396 on(send.len() as u64);
1397 }
1398 return Ok((
1399 PackStats {
1400 object_count: u32::try_from(send.len()).unwrap_or(u32::MAX),
1401 pack_bytes: bytes.len() as u64,
1402 ..PackStats::default()
1403 },
1404 Vec::new(),
1405 ));
1406 }
1407 odb.with_pack_read_context(|| {
1408 let plan = plan_deltas(
1409 odb,
1410 send,
1411 &HashSet::new(),
1412 None,
1413 opts,
1414 name_hashes,
1415 None,
1416 stop,
1417 )?;
1418 stop.poll()?;
1419 let mut index_rows = Vec::with_capacity(plan.entries.len());
1420 let stats = crate::pack_delta::write_pack_with_deltas(
1421 odb,
1422 &plan.entries,
1423 opts,
1424 None,
1425 w,
1426 Some(&mut index_rows),
1427 stop,
1428 on_objects,
1429 )?;
1430 Ok((stats, index_rows))
1431 })
1432}
1433
1434#[allow(clippy::too_many_arguments)]
1437pub(crate) fn write_pack_for_local_fetch(
1438 source_odb: &Odb,
1439 wants: &[ObjectId],
1440 have_odb: &Odb,
1441 haves: &[ObjectId],
1442 source_shallow: &HashSet<ObjectId>,
1443 have_shallow: &HashSet<ObjectId>,
1444 opts: &PackBuildOptions,
1445 stop: &CancelSignal<'_>,
1446 w: &mut dyn Write,
1447) -> Result<PackStats> {
1448 PackObjects::new(have_odb, PackObjectsOptions::from(*opts))
1449 .with_source_odb(source_odb)
1450 .wants(wants)
1451 .haves(haves)
1452 .have_shallow_grafts(have_shallow)
1453 .source_shallow_grafts(source_shallow)
1454 .write_to_with_cancel(w, stop)
1455}
1456
1457fn collect_reachable_excluding(
1458 odb: &Odb,
1459 roots: &[ObjectId],
1460 exclude: &OidHashSet,
1461 skip_missing: bool,
1462 shallow_grafts: &OidHashSet,
1463) -> Result<Vec<ObjectId>> {
1464 let mut visited: OidHashSet = oid_hash_set();
1465 let mut ordered: Vec<ObjectId> = Vec::new();
1466 let mut queue: VecDeque<ObjectId> = VecDeque::new();
1467
1468 let enqueue = |oid: ObjectId,
1469 queue: &mut VecDeque<ObjectId>,
1470 visited: &mut OidHashSet,
1471 ordered: &mut Vec<ObjectId>|
1472 -> bool {
1473 if !visited.insert(oid) {
1474 return false;
1475 }
1476 if !exclude.contains(&oid) {
1477 ordered.push(oid);
1478 }
1479 queue.push_back(oid);
1480 true
1481 };
1482
1483 for &root in roots {
1484 enqueue(root, &mut queue, &mut visited, &mut ordered);
1485 }
1486
1487 let oid_len = odb.hash_algo().len();
1488
1489 while let Some(oid) = queue.pop_front() {
1490 let obj = match odb.read(&oid) {
1491 Ok(o) => o,
1492 Err(_) if skip_missing => continue,
1496 Err(e) => return Err(e),
1497 };
1498 match obj.kind {
1499 ObjectKind::Commit => {
1500 let commit = parse_commit(&obj.data)?;
1501 if !shallow_grafts.contains(&oid) {
1502 for parent in commit.parents {
1503 enqueue(parent, &mut queue, &mut visited, &mut ordered);
1504 }
1505 }
1506 enqueue(commit.tree, &mut queue, &mut visited, &mut ordered);
1507 }
1508 ObjectKind::Tree => {
1509 for entry in TreeRefIter::with_oid_len(&obj.data, oid_len) {
1510 let entry = entry?;
1511 if entry.mode == 0o160000 {
1514 continue;
1515 }
1516 enqueue(entry.oid, &mut queue, &mut visited, &mut ordered);
1517 }
1518 }
1519 ObjectKind::Tag => {
1520 let tag = parse_tag(&obj.data)?;
1521 enqueue(tag.object, &mut queue, &mut visited, &mut ordered);
1522 }
1523 ObjectKind::Blob => {}
1524 }
1525 }
1526
1527 Ok(ordered)
1528}
1529
1530pub(crate) fn pack_type_code(kind: ObjectKind) -> u8 {
1532 match kind {
1533 ObjectKind::Commit => 1,
1534 ObjectKind::Tree => 2,
1535 ObjectKind::Blob => 3,
1536 ObjectKind::Tag => 4,
1537 }
1538}
1539
1540pub(crate) fn encode_pack_object_header(buf: &mut Vec<u8>, type_code: u8, payload_len: usize) {
1543 let mut size = payload_len;
1544 let first = ((type_code & 0x7) << 4) | (size & 0x0f) as u8;
1545 size >>= 4;
1546 if size > 0 {
1547 buf.push(first | 0x80);
1548 while size > 0 {
1549 let b = (size & 0x7f) as u8;
1550 size >>= 7;
1551 buf.push(if size > 0 { b | 0x80 } else { b });
1552 }
1553 } else {
1554 buf.push(first);
1555 }
1556}
1557
1558fn serialize_pack(odb: &Odb, oids: &[ObjectId], opts: &PackBuildOptions) -> Result<Vec<u8>> {
1561 let mut buf = Vec::new();
1562 buf.extend_from_slice(b"PACK");
1563 buf.extend_from_slice(&2u32.to_be_bytes());
1564 let count = u32::try_from(oids.len())
1565 .map_err(|_| Error::CorruptObject("pack object count exceeds u32".to_owned()))?;
1566 buf.extend_from_slice(&count.to_be_bytes());
1567
1568 for oid in oids {
1569 let kind = odb.read_info(oid)?.kind;
1570 write_whole_pack_object_from_odb(&mut buf, odb, *oid, kind, opts)?;
1571 }
1572
1573 append_pack_trailer(&mut buf, odb.hash_algo());
1574 Ok(buf)
1575}
1576
1577fn write_whole_pack_object_from_odb(
1579 buf: &mut Vec<u8>,
1580 odb: &Odb,
1581 oid: ObjectId,
1582 kind: ObjectKind,
1583 opts: &PackBuildOptions,
1584) -> Result<()> {
1585 write_whole_pack_object_to_odb(buf, odb, oid, kind, opts, &CancelSignal::never()).map(|_| ())
1586}
1587
1588pub(crate) fn write_whole_pack_object_to_odb(
1591 w: &mut dyn Write,
1592 odb: &Odb,
1593 oid: ObjectId,
1594 kind: ObjectKind,
1595 opts: &PackBuildOptions,
1596 stop: &CancelSignal<'_>,
1597) -> Result<u64> {
1598 if opts.reuse_objects && odb.hash_algo() == HashAlgo::Sha1 {
1599 if let Some((bytes, start, end)) =
1600 crate::pack::packed_full_object_range(odb.objects_dir(), &oid)?
1601 {
1602 write_all_chunked(w, &bytes[start..end])?;
1603 return Ok((end - start) as u64);
1604 }
1605 }
1606 let size = odb.read_info(&oid)?.size;
1607 let stream_from = odb.big_file_threshold()?;
1608 if kind == ObjectKind::Blob && size >= stream_from {
1609 let mut counter = ByteCounter { inner: w, n: 0 };
1610 odb.with_stream(&oid, |mut stream| {
1611 let payload_len = usize::try_from(stream.size).map_err(|_| {
1612 Error::CorruptObject(format!("object size overflow for {}", oid.to_hex()))
1613 })?;
1614 encode_pack_object_header_to(&mut counter, pack_type_code(kind), payload_len)?;
1615 crate::odb::deflate_stream_to_writer(
1616 &mut stream.reader,
1617 stream.size,
1618 &mut counter,
1619 odb.pack_compression()?,
1620 stop,
1621 )?;
1622 Ok(())
1623 })?
1624 .ok_or_else(|| Error::ObjectNotFound(oid.to_hex()))?;
1625 return Ok(counter.n);
1626 }
1627 let obj = odb.read(&oid)?;
1628 stop.poll()?;
1629 write_whole_pack_object_to(w, odb, oid, kind, &obj.data, opts)
1630}
1631
1632fn encode_pack_object_header_to(w: &mut dyn Write, type_code: u8, size: usize) -> Result<()> {
1633 let mut header = Vec::new();
1634 encode_pack_object_header(&mut header, type_code, size);
1635 w.write_all(&header).map_err(Error::Io)?;
1636 Ok(())
1637}
1638
1639fn write_whole_pack_object(
1641 buf: &mut Vec<u8>,
1642 odb: &Odb,
1643 oid: ObjectId,
1644 kind: ObjectKind,
1645 data: &[u8],
1646 opts: &PackBuildOptions,
1647) -> Result<()> {
1648 write_whole_pack_object_to(buf, odb, oid, kind, data, opts).map(|_| ())
1649}
1650
1651fn write_whole_pack_object_to(
1652 w: &mut dyn Write,
1653 odb: &Odb,
1654 oid: ObjectId,
1655 kind: ObjectKind,
1656 data: &[u8],
1657 opts: &PackBuildOptions,
1658) -> Result<u64> {
1659 let mut counter = ByteCounter { inner: w, n: 0 };
1660 if opts.reuse_objects && odb.hash_algo() == HashAlgo::Sha1 {
1661 if let Some((bytes, start, end)) =
1662 crate::pack::packed_full_object_range(odb.objects_dir(), &oid)?
1663 {
1664 write_all_chunked(&mut counter, &bytes[start..end])?;
1665 return Ok(counter.n);
1666 }
1667 }
1668 encode_pack_object_header_to(&mut counter, pack_type_code(kind), data.len())?;
1669 write_zlib_to(&mut counter, data, odb.pack_compression()?)?;
1670 Ok(counter.n)
1671}
1672
1673pub(crate) fn append_pack_trailer(buf: &mut Vec<u8>, algo: HashAlgo) {
1676 buf.extend_from_slice(&algo.checksum_digest(&*buf));
1677}
1678
1679pub(crate) fn append_whole_object_from_odb(
1683 buf: &mut Vec<u8>,
1684 odb: &Odb,
1685 oid: ObjectId,
1686) -> Result<ObjectKind> {
1687 let obj = odb.read(&oid)?;
1688 write_whole_pack_object(
1689 buf,
1690 odb,
1691 oid,
1692 obj.kind,
1693 &obj.data,
1694 &PackBuildOptions::default(),
1695 )?;
1696 Ok(obj.kind)
1697}
1698
1699pub(crate) struct PlannedEntry {
1701 pub(crate) oid: ObjectId,
1702 pub(crate) kind: ObjectKind,
1703 pub(crate) base: Option<ObjectId>,
1706 pub(crate) searched_delta: Option<(usize, Vec<u8>)>,
1710 pub(crate) reused_delta_zlib:
1713 Option<(u64, std::sync::Arc<crate::pack::PackData>, usize, usize)>,
1714}
1715
1716pub(crate) struct DeltaPlan {
1719 pub(crate) entries: Vec<PlannedEntry>,
1720}
1721
1722pub(crate) struct SerializedDeltaPack {
1724 pub bytes: Vec<u8>,
1725 pub index_rows: Vec<(ObjectId, u64, u32)>,
1726 pub staging_path: Option<std::path::PathBuf>,
1728}
1729
1730fn read_object_infos(
1732 odb: &Odb,
1733 oids: &[ObjectId],
1734 search_threads: Option<usize>,
1735) -> Result<Vec<ObjectInfo>> {
1736 let mut none: Option<&mut dyn FnMut(u64)> = None;
1737 read_object_infos_with_progress(odb, oids, search_threads, &CancelSignal::never(), &mut none)
1738}
1739
1740fn read_object_infos_with_progress(
1741 odb: &Odb,
1742 oids: &[ObjectId],
1743 search_threads: Option<usize>,
1744 stop: &CancelSignal<'_>,
1745 on_objects: &mut Option<&mut dyn FnMut(u64)>,
1746) -> Result<Vec<ObjectInfo>> {
1747 let par = crate::pack_delta::pack_delta_search_parallel_options(odb, search_threads)?;
1748 let read = |oid: &ObjectId| odb.read_info(oid);
1749 let stride = crate::progress::OBJECT_PROGRESS_STRIDE as usize;
1750 if par.threads.get() <= 1 || oids.len() < 32 {
1751 let mut out = Vec::with_capacity(oids.len());
1752 for (i, oid) in oids.iter().enumerate() {
1753 stop.poll()?;
1754 out.push(read(oid)?);
1755 if (i + 1).is_multiple_of(stride) {
1756 if let Some(on) = on_objects.as_deref_mut() {
1757 on(stride as u64);
1758 }
1759 }
1760 }
1761 let rem = oids.len() % stride;
1762 if rem != 0 {
1763 if let Some(on) = on_objects.as_deref_mut() {
1764 on(rem as u64);
1765 }
1766 }
1767 return Ok(out);
1768 }
1769 try_par_map_force(oids, par, read).map_err(|e| match e {
1770 ParallelHashError::Task(err) => err,
1771 })
1772}
1773
1774fn order_bases_first(entries: &mut [PlannedEntry]) {
1780 let n = entries.len();
1781 let bases: Vec<Option<usize>> = {
1782 let position: OidHashMap<usize> = entries
1783 .iter()
1784 .enumerate()
1785 .map(|(i, e)| (e.oid, i))
1786 .collect();
1787 entries
1788 .iter()
1789 .map(|e| e.base.and_then(|b| position.get(&b).copied()))
1790 .collect()
1791 };
1792 let mut dest = vec![usize::MAX; n];
1794 let mut next = 0usize;
1795 let mut chain = Vec::new();
1796 for i in 0..n {
1797 let mut cur = Some(i);
1798 while let Some(j) = cur {
1799 if dest[j] != usize::MAX || chain.len() > n {
1800 break;
1801 }
1802 chain.push(j);
1803 cur = bases[j];
1804 }
1805 while let Some(j) = chain.pop() {
1806 if dest[j] == usize::MAX {
1807 dest[j] = next;
1808 next += 1;
1809 }
1810 }
1811 }
1812 for i in 0..n {
1813 while dest[i] != i {
1814 let d = dest[i];
1815 entries.swap(i, d);
1816 dest.swap(i, d);
1817 }
1818 }
1819}
1820
1821pub(crate) fn objects_dir_has_local_packs(objects_dir: &Path) -> bool {
1823 let pack_dir = objects_dir.join("pack");
1824 if !pack_dir.is_dir() {
1825 return false;
1826 }
1827 std::fs::read_dir(&pack_dir)
1828 .ok()
1829 .into_iter()
1830 .flatten()
1831 .filter_map(|e| e.ok())
1832 .any(|e| e.path().extension().is_some_and(|ext| ext == "pack"))
1833}
1834
1835#[allow(clippy::too_many_arguments)]
1867pub(crate) fn plan_deltas(
1868 odb: &Odb,
1869 send: &[ObjectId],
1870 peer_commits: &HashSet<ObjectId>,
1871 peer_held: Option<&ReachableSet>,
1872 opts: &PackBuildOptions,
1873 name_hashes: &HashMap<ObjectId, u32>,
1874 search_threads: Option<usize>,
1875 stop: &CancelSignal<'_>,
1876) -> Result<DeltaPlan> {
1877 crate::trace::trace_span!("grit.pack.plan_deltas");
1878 let has_local_packs = objects_dir_has_local_packs(odb.objects_dir());
1879 let reuse_on_disk = opts.reuse_deltas && odb.hash_algo() == HashAlgo::Sha1 && has_local_packs;
1880 let search = opts.window > 0 && opts.max_depth > 0;
1881 stop.poll()?;
1882 let infos = read_object_infos(odb, send, search_threads)?;
1883 stop.poll()?;
1884 let in_pack: HashSet<ObjectId> = if reuse_on_disk || opts.thin || opts.respect_islands {
1885 send.iter().copied().collect()
1886 } else {
1887 HashSet::new()
1888 };
1889
1890 let islands = load_islands_for_pack(odb, &in_pack, opts);
1894
1895 let peer_has = |oid: &ObjectId| peer_held.is_some_and(|held| held.contains(oid));
1897 let thin_bases = if opts.thin && (reuse_on_disk || search) {
1898 crate::pack_thin_bases::find_thin_bases(
1899 odb,
1900 send,
1901 &infos,
1902 &|oid| in_pack.contains(oid),
1903 &|oid| peer_commits.contains(oid) || peer_has(oid),
1904 name_hashes,
1905 opts.window.max(1),
1906 )?
1907 } else {
1908 crate::pack_thin_bases::ThinBases::default()
1909 };
1910
1911 let mut delta_to_base: OidHashMap<ObjectId> = oid_hash_map();
1913 let mut reused_zlib: HashMap<ObjectId, crate::pack::OnDiskReuseZlibEntry> = HashMap::new();
1915
1916 if reuse_on_disk {
1918 let reusable_base = |oid: &ObjectId| {
1921 in_pack.contains(oid) || (opts.thin && (thin_bases.oids.contains(oid) || peer_has(oid)))
1922 };
1923 reused_zlib = crate::pack::collect_packed_delta_reuse_for_send(
1924 odb.objects_dir(),
1925 &in_pack,
1926 &reusable_base,
1927 )?;
1928 stop.poll()?;
1929 reused_zlib.retain(|t, (base, ..)| {
1930 let far_peer_base = !in_pack.contains(base) && !thin_bases.oids.contains(base);
1933 !(far_peer_base && search && thin_bases.targets.contains(t))
1934 && *base != *t
1935 && islands.in_same_island(t, base)
1936 });
1937 delta_to_base.extend(reused_zlib.iter().map(|(t, (base, ..))| (*t, *base)));
1938 for (oid, info) in send.iter().zip(&infos) {
1940 if info.kind == ObjectKind::Blob && info.size == 0 && reused_zlib.remove(oid).is_some()
1941 {
1942 delta_to_base.remove(oid);
1943 }
1944 }
1945 }
1946
1947 let reuse_dependents: OidHashMap<usize> = {
1952 let mut longest: OidHashMap<usize> = oid_hash_map();
1953 let ends = crate::pack::delta_chain_ends(
1954 delta_to_base.keys().copied(),
1955 delta_to_base.len(),
1956 |oid| delta_to_base.get(oid).copied(),
1957 );
1958 for target in reused_zlib.keys() {
1959 let (end, depth) = match ends.as_ref().and_then(|ends| ends.get(target)) {
1960 Some((end, depth)) => (end, depth as usize),
1961 None => {
1962 let mut cur = *target;
1963 let mut depth = 0usize;
1964 while let Some(base) = delta_to_base.get(&cur) {
1965 depth += 1;
1966 cur = *base;
1967 if depth > delta_to_base.len() {
1968 break;
1969 }
1970 }
1971 (cur, depth)
1972 }
1973 };
1974 let entry = longest.entry(end).or_insert(0);
1975 *entry = (*entry).max(depth);
1976 }
1977 longest
1978 };
1979
1980 let mut searched: OidHashMap<(usize, Vec<u8>)> = oid_hash_map();
1981 if search {
1982 let found = search_new_deltas(
1983 odb,
1984 send,
1985 &infos,
1986 &thin_bases,
1987 |oid| {
1988 delta_to_base.contains_key(oid)
1989 || (thin_bases.oids.is_empty() && reuse_dependents.contains_key(oid))
1990 },
1991 &reuse_dependents,
1992 name_hashes,
1993 &islands,
1994 opts,
1995 search_threads,
1996 has_local_packs,
1997 stop.flag(),
1998 )?;
1999 stop.poll()?;
2000 for (target, base, delta) in found {
2001 delta_to_base.insert(target, base);
2002 searched.insert(target, delta);
2003 }
2004 }
2005 drop((in_pack, thin_bases));
2006
2007 if opts.max_depth > 0 && !delta_to_base.is_empty() {
2008 let cuts = crate::pack::delta_chain_cuts(
2013 delta_to_base.keys().copied(),
2014 delta_to_base.len(),
2015 |oid| delta_to_base.get(oid).copied(),
2016 opts.max_depth,
2017 );
2018 match cuts {
2019 Some(cuts) => {
2020 for target in cuts {
2021 delta_to_base.remove(&target);
2022 }
2023 }
2024 None => {
2025 let mut delta_std: HashMap<ObjectId, ObjectId> = delta_to_base.drain().collect();
2026 crate::pack::cap_delta_chain_depth(&mut delta_std, opts.max_depth);
2027 delta_to_base.extend(delta_std);
2028 }
2029 }
2030 }
2031
2032 let mut entries: Vec<PlannedEntry> = send
2036 .iter()
2037 .zip(&infos)
2038 .map(|(&oid, info)| {
2039 let base = delta_to_base.remove(&oid);
2040 let (searched_delta, reused_delta_zlib) = match base {
2041 Some(_) => (
2042 searched.remove(&oid),
2043 reused_zlib
2044 .remove(&oid)
2045 .map(|(_, size, bytes, start, end)| (size, bytes, start, end)),
2046 ),
2047 None => (None, None),
2048 };
2049 PlannedEntry {
2050 oid,
2051 kind: info.kind,
2052 base,
2053 searched_delta,
2054 reused_delta_zlib,
2055 }
2056 })
2057 .collect();
2058 drop((infos, delta_to_base, searched, reused_zlib));
2059 order_bases_first(&mut entries);
2060 Ok(DeltaPlan { entries })
2061}
2062
2063type SearchedDelta = (ObjectId, ObjectId, (usize, Vec<u8>));
2065
2066#[allow(clippy::too_many_arguments)]
2075fn search_new_deltas(
2076 odb: &Odb,
2077 send: &[ObjectId],
2078 infos: &[ObjectInfo],
2079 thin_bases: &crate::pack_thin_bases::ThinBases,
2080 skip: impl Fn(&ObjectId) -> bool,
2081 reuse_dependents: &OidHashMap<usize>,
2082 name_hashes: &HashMap<ObjectId, u32>,
2083 islands: &crate::delta_islands::DeltaIslands,
2084 opts: &PackBuildOptions,
2085 search_threads: Option<usize>,
2086 has_local_packs: bool,
2087 stop: &std::sync::atomic::AtomicBool,
2088) -> Result<Vec<SearchedDelta>> {
2089 u32::try_from(send.len() + thin_bases.bases.len())
2090 .map_err(|_| Error::CorruptObject("too many objects for one pack".to_owned()))?;
2091 let ids: Vec<u32> = (0..send.len())
2092 .filter(|&i| infos[i].size > 0 && !skip(&send[i]))
2093 .map(|i| i as u32)
2094 .collect();
2095 let packed = if opts.reuse_deltas && has_local_packs {
2097 let oids: OidHashSet = ids.iter().map(|&i| send[i as usize]).collect();
2098 crate::pack::locate_packed_objects(odb.objects_dir(), &oids).ok()
2099 } else {
2100 None
2101 };
2102 let base_oids: Vec<ObjectId> = thin_bases.bases.iter().map(|b| b.oid).collect();
2103 let base_infos = read_object_infos(odb, &base_oids, search_threads)?;
2104 let mut candidates: Vec<crate::delta_search::DeltaCandidate> = ids
2105 .into_iter()
2106 .map(|id| {
2107 let oid = send[id as usize];
2108 let info = infos[id as usize];
2109 let slot = packed.as_ref().and_then(|p| p.get(&oid));
2110 crate::delta_search::DeltaCandidate {
2111 id,
2112 oid,
2113 kind: info.kind,
2114 size: info.size,
2115 name_hash: name_hashes.get(&oid).copied().unwrap_or(0),
2116 whole_in_pack: slot.filter(|s| s.whole).map(|s| s.pack),
2117 in_pack: slot.map(|s| s.pack),
2118 dependent_depth: reuse_dependents.get(&oid).copied().unwrap_or(0),
2119 base_only: false,
2120 }
2121 })
2122 .collect();
2123 drop(packed);
2124 candidates.extend(
2125 (send.len()..)
2126 .zip(thin_bases.bases.iter().zip(&base_infos))
2127 .filter(|(_, (base, info))| info.kind == base.kind && info.size > 0)
2128 .map(|(id, (base, info))| crate::delta_search::DeltaCandidate {
2129 id: id as u32,
2130 oid: base.oid,
2131 kind: info.kind,
2132 size: info.size,
2133 name_hash: base.name_hash,
2134 whole_in_pack: None,
2135 in_pack: None,
2136 dependent_depth: 0,
2137 base_only: true,
2138 }),
2139 );
2140 let oid_of = |id: u32| {
2141 let id = id as usize;
2142 send.get(id)
2143 .copied()
2144 .unwrap_or_else(|| base_oids[id - send.len()])
2145 };
2146 let window_memory = match opts.window_memory {
2147 Some(bytes) => bytes,
2148 None => crate::pack_delta::pack_config(odb)?.pack_window_memory_bytes(),
2149 };
2150 let threads = crate::pack_delta::pack_delta_search_parallel_options(odb, search_threads)?;
2151 let allowed = |target: &ObjectId, base: &ObjectId| {
2152 islands.in_same_island(target, base)
2153 && (!reuse_dependents.contains_key(target) || thin_bases.oids.contains(base))
2154 };
2155 let found = crate::delta_search::search_deltas(
2156 odb,
2157 candidates,
2158 crate::delta_search::DeltaSearchParams {
2159 window: opts.window,
2160 max_depth: opts.max_depth,
2161 window_memory,
2162 hash_len: odb.hash_algo().len(),
2163 compression: odb.pack_compression()?,
2164 threads: threads.threads.get(),
2165 },
2166 &allowed,
2167 stop,
2168 )?;
2169 Ok(found
2170 .into_iter()
2171 .map(|d| (oid_of(d.target), oid_of(d.base), (d.delta_len, d.zlib)))
2172 .collect())
2173}
2174
2175fn load_islands_for_pack(
2181 odb: &Odb,
2182 in_pack: &HashSet<ObjectId>,
2183 opts: &PackBuildOptions,
2184) -> crate::delta_islands::DeltaIslands {
2185 if !opts.respect_islands {
2186 return crate::delta_islands::DeltaIslands::default();
2187 }
2188 let Some(git_dir) = odb.config_git_dir() else {
2189 return crate::delta_islands::DeltaIslands::default();
2190 };
2191 let Ok(repo) = crate::repo::Repository::open(git_dir, &RepositoryOptions::empty()) else {
2192 return crate::delta_islands::DeltaIslands::default();
2193 };
2194 let env = crate::environment::Environment::empty();
2195 let cfg = crate::config::ConfigSet::load(&env, Some(git_dir), true).unwrap_or_default();
2196 crate::delta_islands::load_delta_islands(&repo, &cfg, in_pack)
2197}
2198
2199fn write_zlib(buf: &mut Vec<u8>, data: &[u8], compression: Compression) -> Result<()> {
2201 write_zlib_to(buf, data, compression)
2202}
2203
2204fn write_zlib_to(w: &mut dyn Write, data: &[u8], compression: Compression) -> Result<()> {
2205 let mut enc = ZlibEncoder::new(w, compression);
2206 enc.write_all(data).map_err(Error::Io)?;
2207 enc.finish().map_err(Error::Io)?;
2208 Ok(())
2209}
2210
2211pub(crate) fn encode_ofs_delta_distance(buf: &mut Vec<u8>, mut ofs: u64) {
2214 let mut dheader = [0u8; 32];
2215 let mut pos = dheader.len() - 1;
2216 dheader[pos] = (ofs & 0x7f) as u8;
2217 while {
2218 ofs >>= 7;
2219 ofs != 0
2220 } {
2221 pos -= 1;
2222 ofs -= 1;
2223 dheader[pos] = 0x80 | ((ofs & 0x7f) as u8);
2224 }
2225 buf.extend_from_slice(&dheader[pos..]);
2226}
2227
2228#[cfg(test)]
2229#[allow(clippy::unwrap_used, clippy::expect_used)]
2230pub(crate) mod tests {
2231 use super::*;
2232 use crate::pack_object_select::{enumerate_pack_objects, PackEnumerateOptions};
2233
2234 fn planned(n: u8, base: Option<u8>) -> PlannedEntry {
2235 let oid = |n: u8| ObjectId::from_bytes(&[n; 20]).unwrap();
2236 PlannedEntry {
2237 oid: oid(n),
2238 kind: ObjectKind::Blob,
2239 base: base.map(oid),
2240 searched_delta: None,
2241 reused_delta_zlib: None,
2242 }
2243 }
2244
2245 fn order_of(mut entries: Vec<PlannedEntry>) -> Vec<u8> {
2246 order_bases_first(&mut entries);
2247 entries.iter().map(|e| e.oid.as_bytes()[0]).collect()
2248 }
2249
2250 #[test]
2251 fn bases_are_ordered_before_their_targets() {
2252 let entries = vec![
2254 planned(1, Some(3)),
2255 planned(2, None),
2256 planned(3, Some(5)),
2257 planned(4, Some(9)),
2258 planned(5, None),
2259 planned(6, Some(2)),
2260 ];
2261 assert_eq!(order_of(entries), [5, 3, 1, 2, 4, 6]);
2262 }
2263
2264 #[test]
2265 fn base_cycles_do_not_hang_or_drop_entries() {
2266 let entries = vec![planned(1, Some(2)), planned(2, Some(1)), planned(3, None)];
2267 let mut order = order_of(entries);
2268 order.sort_unstable();
2269 assert_eq!(order, [1, 2, 3]);
2270 }
2271
2272 pub(crate) fn packed_selection(
2274 commits: usize,
2275 ) -> (
2276 tempfile::TempDir,
2277 Repository,
2278 crate::pack_object_select::PackObjectSelection,
2279 ) {
2280 let tmp = tempfile::tempdir().unwrap();
2281 let path = grit_test_support::git_packed_history(tmp.path(), commits);
2282 let tip = grit_test_support::git(&path, &["rev-parse", "main"]);
2283 let tip = ObjectId::from_hex(tip.trim()).unwrap();
2284 let repo = Repository::open(&path, &RepositoryOptions::empty()).unwrap();
2285 let empty = HashSet::new();
2286 let selection = enumerate_pack_objects(
2287 repo.odb(),
2288 &[tip],
2289 &[],
2290 &PackEnumerateOptions {
2291 filter: None,
2292 shallow_grafts: &empty,
2293 have_shallow_grafts: Some(&empty),
2294 use_bitmaps: false,
2295 exclude_objects: None,
2296 peer_odb: None,
2297 have_boundary: HaveBoundary::CommitClosure,
2298 },
2299 )
2300 .unwrap();
2301 (tmp, repo, selection)
2302 }
2303
2304 #[cfg_attr(
2305 not(feature = "full-tests"),
2306 ignore = "starts system git or is heavy; run with --features full-tests"
2307 )]
2308 #[test]
2309 fn serve_plan_reuses_on_disk_deltas() {
2310 let (_tmp, repo, selection) = packed_selection(80);
2311 let opts = PackBuildOptions::for_serve(None, false, true);
2312 let plan = plan_deltas(
2313 repo.odb(),
2314 &selection.send,
2315 &selection.have_closure,
2316 selection.peer_held.as_deref(),
2317 &opts,
2318 &selection.name_hashes,
2319 None,
2320 &CancelSignal::never(),
2321 )
2322 .unwrap();
2323
2324 let reused = plan
2325 .entries
2326 .iter()
2327 .filter(|e| e.reused_delta_zlib.is_some())
2328 .count();
2329 let trees_and_blobs = plan
2330 .entries
2331 .iter()
2332 .filter(|e| matches!(e.kind, ObjectKind::Tree | ObjectKind::Blob))
2333 .count();
2334 assert!(
2335 reused * 2 > trees_and_blobs,
2336 "most trees and blobs keep their on-disk delta ({reused} of {trees_and_blobs})"
2337 );
2338 }
2339
2340 fn loose_history(commits: usize) -> (tempfile::TempDir, Repository, ObjectId) {
2343 let tmp = tempfile::tempdir().unwrap();
2344 let dir = tmp.path();
2345 grit_test_support::git(dir, &["init", "-q", "-b", "main"]);
2346 std::fs::create_dir_all(dir.join("src")).unwrap();
2347 for i in 0..20 {
2348 std::fs::write(
2349 dir.join(format!("src/stable{i}.txt")),
2350 format!("stable {i}\n"),
2351 )
2352 .unwrap();
2353 }
2354 let mut lines: Vec<String> = (0..200).map(|i| format!("line {i} of the file")).collect();
2355 for c in 0..commits {
2356 lines[(c * 13) % 200] = format!("changed in commit {c}");
2357 std::fs::write(dir.join("src/file.txt"), lines.join("\n")).unwrap();
2358 std::fs::write(dir.join("src/other.txt"), format!("other {c}\n")).unwrap();
2359 grit_test_support::git(dir, &["add", "."]);
2360 grit_test_support::git(
2361 dir,
2362 &[
2363 "-c",
2364 "user.name=T",
2365 "-c",
2366 "user.email=t@e",
2367 "commit",
2368 "-qm",
2369 "c",
2370 ],
2371 );
2372 }
2373 let tip = grit_test_support::git(dir, &["rev-parse", "main"]);
2374 let tip = ObjectId::from_hex(tip.trim()).unwrap();
2375 let repo = Repository::open(&dir.join(".git"), &RepositoryOptions::empty()).unwrap();
2376 (tmp, repo, tip)
2377 }
2378
2379 #[cfg_attr(
2380 not(feature = "full-tests"),
2381 ignore = "starts system git or is heavy; run with --features full-tests"
2382 )]
2383 #[test]
2384 fn loose_plan_deltifies_trees_and_blobs_with_compressed_search_output() {
2385 let (tmp, repo, tip) = loose_history(12);
2386 assert!(!objects_dir_has_local_packs(repo.odb().objects_dir()));
2387 let empty = HashSet::new();
2388 let opts = PackBuildOptions::for_local_copy(None);
2389 let selection = crate::pack_object_select::enumerate_pack_objects_naming(
2390 repo.odb(),
2391 &[tip],
2392 &[],
2393 &PackEnumerateOptions {
2394 filter: None,
2395 shallow_grafts: &empty,
2396 have_shallow_grafts: Some(&empty),
2397 use_bitmaps: false,
2398 exclude_objects: None,
2399 peer_odb: None,
2400 have_boundary: HaveBoundary::CommitClosure,
2401 },
2402 NameTrees::for_delta_search(&opts),
2403 false,
2404 &CancelSignal::never(),
2405 )
2406 .unwrap();
2407 let plan = plan_deltas(
2408 repo.odb(),
2409 &selection.send,
2410 &selection.have_closure,
2411 selection.peer_held.as_deref(),
2412 &opts,
2413 &selection.name_hashes,
2414 Some(1),
2415 &CancelSignal::never(),
2416 )
2417 .unwrap();
2418 let deltas_of = |kind| {
2419 plan.entries
2420 .iter()
2421 .filter(|e| e.kind == kind && e.base.is_some())
2422 .count()
2423 };
2424 assert!(deltas_of(ObjectKind::Blob) >= 10, "blob deltas");
2425 assert!(deltas_of(ObjectKind::Tree) >= 10, "subtree deltas");
2426 assert!(plan
2427 .entries
2428 .iter()
2429 .filter(|e| e.base.is_some())
2430 .all(|e| e.searched_delta.is_some()));
2431
2432 let pack = crate::pack_delta::serialize_pack_with_deltas(
2433 repo.odb(),
2434 &plan.entries,
2435 &opts,
2436 Some(2),
2437 )
2438 .unwrap();
2439 std::fs::write(tmp.path().join("plan.pack"), &pack.bytes).unwrap();
2440 grit_test_support::git(tmp.path(), &["index-pack", "--strict", "plan.pack"]);
2441 }
2442
2443 #[test]
2444 fn serve_options_follow_pack_config() {
2445 use crate::config::{ConfigFile, ConfigScope, ConfigSet};
2446 let file = ConfigFile::parse(
2447 Path::new(".git/config"),
2448 "[pack]\n\twindow = 4\n\tdepth = 0\n",
2449 ConfigScope::Local,
2450 )
2451 .unwrap();
2452 let mut cfg = ConfigSet::new();
2453 cfg.merge(&file);
2454 let opts = PackBuildOptions::for_serve(Some(&cfg), true, false);
2455 assert_eq!((opts.window, opts.max_depth), (4, 0));
2456 assert!(opts.thin && !opts.use_ofs_delta && opts.delta);
2457 assert!(
2458 opts.reuse_deltas,
2459 "like git upload-pack, depth 0 stops new deltas but still sends on-disk deltas"
2460 );
2461
2462 let defaults = PackBuildOptions::for_serve(None, false, true);
2463 assert_eq!(
2464 (defaults.window, defaults.max_depth),
2465 (10, crate::pack::DEFAULT_PACK_DEPTH)
2466 );
2467 assert!(defaults.reuse_deltas && defaults.reuse_objects && !defaults.thin);
2468 }
2469
2470 struct PeerHoldsSentBlob {
2474 dir: tempfile::TempDir,
2475 repo: Repository,
2476 old: ObjectId,
2477 new: ObjectId,
2478 peer_objects: Vec<ObjectId>,
2480 peer_commits: HashSet<ObjectId>,
2481 send: Vec<ObjectId>,
2482 name_hashes: HashMap<ObjectId, u32>,
2483 }
2484
2485 impl PeerHoldsSentBlob {
2486 fn new() -> Self {
2487 use crate::objects::{serialize_tree, TreeEntry};
2488 use crate::pack_name_hash::pack_name_hash;
2489 let dir = tempfile::TempDir::new().unwrap();
2490 let repo =
2491 Repository::init(dir.path(), &crate::repo::InitOptions::default()).expect("init");
2492 let odb = repo.odb();
2493 let tree = |entries: &[(&str, ObjectId)]| {
2494 let entries: Vec<TreeEntry> = entries
2495 .iter()
2496 .map(|&(name, oid)| TreeEntry {
2497 mode: 0o100644,
2498 name: name.as_bytes().to_vec(),
2499 oid,
2500 })
2501 .collect();
2502 odb.write(ObjectKind::Tree, &serialize_tree(&entries))
2503 .unwrap()
2504 };
2505 let commit = |tree: ObjectId, parents: &[ObjectId]| {
2506 let parents: String = parents
2507 .iter()
2508 .map(|p| format!("parent {}\n", p.to_hex()))
2509 .collect();
2510 let body = format!(
2511 "tree {}\n{parents}author A <a@example.com> 0 +0000\ncommitter A <a@example.com> 0 +0000\n\nc\n",
2512 tree.to_hex()
2513 );
2514 odb.write(ObjectKind::Commit, body.as_bytes()).unwrap()
2515 };
2516 let lines: Vec<String> = (0..200).map(|i| format!("line {i} of f.txt")).collect();
2517 let old = odb
2518 .write(ObjectKind::Blob, lines.join("\n").as_bytes())
2519 .unwrap();
2520 let new = odb
2521 .write(
2522 ObjectKind::Blob,
2523 format!("{}\nmore\n", lines.join("\n")).as_bytes(),
2524 )
2525 .unwrap();
2526 let old_tree = tree(&[("f.txt", old)]);
2527 let new_tree = tree(&[("f.txt", new)]);
2528 let first = commit(old_tree, &[]);
2529 let second = commit(new_tree, &[]);
2530 let extra = odb.write(ObjectKind::Blob, b"extra\n").unwrap();
2531 let merge_tree = tree(&[("extra.txt", extra), ("f.txt", new)]);
2532 let merge = commit(merge_tree, &[first, second]);
2533 Self {
2534 dir,
2535 old,
2536 new,
2537 peer_objects: vec![old, new, old_tree, new_tree, first, second],
2538 peer_commits: HashSet::from([first, second]),
2539 send: vec![merge, merge_tree, new, extra],
2540 name_hashes: HashMap::from([
2541 (new, pack_name_hash("f.txt")),
2542 (extra, pack_name_hash("extra.txt")),
2543 ]),
2544 repo,
2545 }
2546 }
2547
2548 fn thin_plan(&self) -> DeltaPlan {
2549 plan_deltas(
2550 self.repo.odb(),
2551 &self.send,
2552 &self.peer_commits,
2553 None,
2554 &PackBuildOptions::for_serve(None, true, true),
2555 &self.name_hashes,
2556 Some(1),
2557 &CancelSignal::never(),
2558 )
2559 .unwrap()
2560 }
2561 }
2562
2563 #[test]
2564 fn thin_pack_never_deltas_a_sent_blob_against_itself() {
2565 let fx = PeerHoldsSentBlob::new();
2566 let plan = fx.thin_plan();
2567 assert_eq!(plan.entries.len(), fx.send.len());
2568 for entry in &plan.entries {
2569 assert_ne!(
2570 entry.base,
2571 Some(entry.oid),
2572 "a delta against itself never resolves"
2573 );
2574 }
2575 let new = plan.entries.iter().find(|e| e.oid == fx.new).unwrap();
2576 assert_eq!(new.base, Some(fx.old), "the other held version is the base");
2577 }
2578
2579 #[cfg_attr(
2580 not(feature = "full-tests"),
2581 ignore = "starts system git or is heavy; run with --features full-tests"
2582 )]
2583 #[test]
2584 fn thin_pack_with_held_sent_blob_completes_in_git() {
2585 let fx = PeerHoldsSentBlob::new();
2586 let plan = fx.thin_plan();
2587 let opts = PackBuildOptions::for_serve(None, true, true);
2588 let pack = crate::pack_delta::serialize_pack_with_deltas(
2589 fx.repo.odb(),
2590 &plan.entries,
2591 &opts,
2592 Some(1),
2593 )
2594 .unwrap();
2595
2596 let root = fx.dir.path();
2597 grit_test_support::git(root, &["init", "-q", "--bare", "peer"]);
2598 let peer = root.join("peer");
2599 for oid in &fx.peer_objects {
2600 let obj = fx.repo.odb().read(oid).unwrap();
2601 let written = grit_test_support::git_cmd(&[
2602 "hash-object",
2603 "-w",
2604 "--stdin",
2605 "-t",
2606 obj.kind.as_str(),
2607 ])
2608 .in_dir(&peer)
2609 .stdin(obj.data)
2610 .suc();
2611 assert_eq!(written.stdout.trim(), oid.to_hex());
2612 }
2613 grit_test_support::git_cmd(&["index-pack", "--strict", "--fix-thin", "--stdin"])
2614 .in_dir(&peer)
2615 .stdin(pack.bytes)
2616 .suc();
2617 let merge = fx.send[0].to_hex();
2618 grit_test_support::git(&peer, &["update-ref", "refs/heads/main", &merge]);
2619 grit_test_support::git(&peer, &["fsck", "--strict", "--no-dangling"]);
2620 }
2621}