1#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used))]
4
5use flate2::{Compress, Compression, FlushCompress, Status};
6use sley_core::{
7 ByteBudget, CancelFlag, GitError, ObjectFormat, ObjectId, ResourceLimitKind, Result,
8 StreamingDigest,
9 primitives::{u16_be, u32_be, u64_be},
10};
11use sley_formats::Bundle;
12use sley_object::{EncodedObject, ObjectType};
13use std::borrow::Borrow;
14use std::cell::RefCell;
15use std::cmp::Ordering;
16use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
17use std::fmt;
18use std::io::Write;
19use std::ops::Range;
20use std::sync::Arc;
21
22mod bounded_read;
29mod delta;
30mod fix_thin;
31mod index;
32pub mod inflate;
33mod limits;
34mod parallel_index;
35mod read;
36mod write;
37
38pub use bounded_read::*;
39pub(crate) use delta::*;
40pub use fix_thin::*;
41pub use index::*;
42pub use limits::{MAX_READ_DELTA_CHAIN_DEPTH, PACK_OBJECT_COUNT_PREALLOC_CAP};
43pub(crate) use limits::{checked_pack_object_count, pack_entry_prealloc};
44pub use parallel_index::*;
45pub use read::*;
46pub use write::*;
47
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct PackEntry {
50 pub oid: ObjectId,
51 pub compressed_size: u64,
52 pub uncompressed_size: u64,
53 pub offset: u64,
54}
55#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct RepackPolicy {
57 pub write_bitmaps: bool,
58 pub cruft_packs: bool,
59 pub geometric_factor: Option<u8>,
60}
61
62#[derive(Debug, Clone, PartialEq, Eq)]
63pub struct PackFile {
64 pub version: u32,
65 pub entries: Vec<PackObject>,
66 pub checksum: ObjectId,
67}
68
69#[derive(Debug, Clone, PartialEq, Eq)]
70pub struct PackObject {
71 pub entry: PackEntry,
72 pub object: EncodedObject,
73}
74
75#[derive(Debug, Clone, PartialEq, Eq)]
78pub struct PackVerifyStat {
79 pub oid: ObjectId,
81 pub object_type: ObjectType,
83 pub size: u64,
85 pub size_in_pack: u64,
88 pub offset: u64,
90 pub delta_depth: u32,
92 pub base_oid: Option<ObjectId>,
95}
96
97#[derive(Debug, Clone, PartialEq, Eq)]
100pub struct PackVerifyStats {
101 pub objects: Vec<PackVerifyStat>,
102 pub checksum: ObjectId,
103}
104
105#[derive(Debug, Clone, PartialEq, Eq)]
106pub struct PackWrite {
107 pub pack: Vec<u8>,
108 pub index: Vec<u8>,
109 pub checksum: ObjectId,
110 pub entries: Vec<PackIndexEntry>,
111 pub delta_count: u32,
112}
113
114#[derive(Debug, Clone, PartialEq, Eq)]
115pub struct PackWriteSummary {
116 pub index: Vec<u8>,
117 pub checksum: ObjectId,
118 pub entries: Vec<PackIndexEntry>,
119 pub delta_count: u32,
120 pub pack_size: u64,
121 pub peak_working_set_bytes: u64,
125}
126
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
128pub struct PackInput<'a> {
129 pub oid: &'a ObjectId,
130 pub object: &'a EncodedObject,
131}
132
133#[derive(Debug, Clone, Copy, Default)]
136pub struct PackPlanningHints<'a> {
137 name_hashes: Option<&'a HashMap<ObjectId, u32>>,
138}
139
140impl<'a> PackPlanningHints<'a> {
141 pub const fn new() -> Self {
142 Self { name_hashes: None }
143 }
144
145 pub const fn with_name_hashes(mut self, name_hashes: &'a HashMap<ObjectId, u32>) -> Self {
148 self.name_hashes = Some(name_hashes);
149 self
150 }
151
152 pub(crate) const fn name_hashes(self) -> Option<&'a HashMap<ObjectId, u32>> {
153 self.name_hashes
154 }
155}
156
157pub fn sort_by_pack_planning_order<T>(
163 items: &mut [T],
164 mut metadata: impl FnMut(&T) -> (ObjectId, ObjectType, u64, u32),
165) {
166 items.sort_by(|left, right| compare_pack_planning_metadata(metadata(left), metadata(right)));
167}
168
169fn compare_pack_planning_metadata(
170 left: (ObjectId, ObjectType, u64, u32),
171 right: (ObjectId, ObjectType, u64, u32),
172) -> Ordering {
173 pack_planning_type_rank(left.1)
174 .cmp(&pack_planning_type_rank(right.1))
175 .then_with(|| right.3.cmp(&left.3))
176 .then_with(|| right.2.cmp(&left.2))
177 .then_with(|| left.0.as_bytes().cmp(right.0.as_bytes()))
178}
179
180const fn pack_planning_type_rank(object_type: ObjectType) -> u8 {
181 match object_type {
182 ObjectType::Tag => 0,
183 ObjectType::Blob => 1,
184 ObjectType::Tree => 2,
185 ObjectType::Commit => 3,
186 }
187}
188#[derive(Debug, Clone, Copy, PartialEq, Eq)]
189enum PackObjectKind {
190 Commit,
191 Tree,
192 Blob,
193 Tag,
194 OfsDelta,
195 RefDelta,
196}
197
198#[derive(Debug, Clone, PartialEq, Eq)]
199enum DeltaBase {
200 Offset(u64),
201 Ref(ObjectId),
202}
203
204struct OnDiskEntry {
208 offset: u64,
209 base: Option<DeltaBase>,
210 stream_size: u64,
211}
212#[derive(Debug, Clone, Copy, PartialEq, Eq)]
213struct EntryHeader {
214 kind: PackObjectKind,
215 size: u64,
216}
217fn next_byte(bytes: &[u8], offset: &mut usize) -> Result<u8> {
218 let Some(byte) = bytes.get(*offset).copied() else {
219 return Err(GitError::InvalidFormat(
220 "truncated pack entry header".into(),
221 ));
222 };
223 *offset += 1;
224 Ok(byte)
225}
226
227fn checked_range(
228 start: usize,
229 count: usize,
230 width: usize,
231 total: usize,
232) -> Result<std::ops::Range<usize>> {
233 let len = count
234 .checked_mul(width)
235 .ok_or_else(|| GitError::InvalidFormat("pack index table overflow".into()))?;
236 let end = start
237 .checked_add(len)
238 .ok_or_else(|| GitError::InvalidFormat("pack index table overflow".into()))?;
239 if end > total {
240 return Err(GitError::InvalidFormat("truncated pack index table".into()));
241 }
242 Ok(start..end)
243}
244
245fn validate_position_permutation(positions: &[u32]) -> Result<()> {
246 let mut seen = vec![false; positions.len()];
247 for position in positions {
248 let idx = *position as usize;
249 if idx >= positions.len() {
250 return Err(GitError::InvalidFormat(format!(
251 "invalid rev-index position {position}"
252 )));
253 }
254 if seen[idx] {
255 return Err(GitError::InvalidFormat(format!(
256 "invalid rev-index position {position}"
257 )));
258 }
259 seen[idx] = true;
260 }
261 Ok(())
262}
263
264thread_local! {
270 pub(crate) static INFLATE: RefCell<flate2::Decompress> = RefCell::new(flate2::Decompress::new(true));
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276 use flate2::Compression;
277 use flate2::read::ZlibDecoder;
278 use flate2::write::ZlibEncoder;
279 use sley_core::AtomicCancel;
280 use std::fs;
281 use std::io::{Read, Write};
282 use std::path::{Path, PathBuf};
283 use std::process::Command;
284 use std::time::{SystemTime, UNIX_EPOCH};
285
286 fn multi_blob_pack(count: usize) -> Vec<u8> {
287 let objects = (0..count)
288 .map(|idx| {
289 EncodedObject::new(
290 ObjectType::Blob,
291 format!("stream cancel pack object {idx}\n").into_bytes(),
292 )
293 })
294 .collect::<Vec<_>>();
295 PackFile::write_undeltified(&objects, ObjectFormat::Sha1)
296 .expect("test operation should succeed")
297 .pack
298 }
299
300 #[test]
301 fn parallel_index_already_cancelled_returns_cancelled() {
302 let pack = multi_blob_pack(4);
303 let source = AtomicCancel::new();
304 source.cancel();
305 let err = PackIndex::write_v2_for_pack_with_options(
306 &pack,
307 ObjectFormat::Sha1,
308 |_| Ok(None),
309 PackIndexOptions::default(),
310 CancelFlag::new(&source),
311 |_| {},
312 )
313 .expect_err("pre-cancelled index should fail");
314 assert_eq!(err, GitError::Cancelled);
315 }
316
317 #[test]
318 fn parallel_index_is_identical_with_one_or_many_workers() {
319 let pack = multi_blob_pack(128);
320 let serial_schedule = PackIndex::write_v2_for_pack_with_options(
321 &pack,
322 ObjectFormat::Sha1,
323 |_| Ok(None),
324 PackIndexOptions::default().with_threads(1),
325 CancelFlag::never(),
326 |_| {},
327 )
328 .expect("one-worker schedule");
329 let parallel_schedule = PackIndex::write_v2_for_pack_with_options(
330 &pack,
331 ObjectFormat::Sha1,
332 |_| Ok(None),
333 PackIndexOptions::default().with_threads(32),
334 CancelFlag::never(),
335 |_| {},
336 )
337 .expect("many-worker schedule");
338 assert_eq!(parallel_schedule, serial_schedule);
339 }
340
341 #[test]
342 fn write_packed_from_source_respects_cancel_between_windows() {
343 let format = ObjectFormat::Sha1;
344 let count = 4u32;
345 let objects = (0..count)
346 .map(|idx| {
347 EncodedObject::new(
348 ObjectType::Blob,
349 format!("write-cancel object {idx}\n").into_bytes(),
350 )
351 })
352 .collect::<Vec<_>>();
353 let object_ids = objects
354 .iter()
355 .map(|object| object.object_id(format).expect("oid"))
356 .collect::<Vec<_>>();
357 let object_map = object_ids
358 .iter()
359 .copied()
360 .zip(objects.into_iter().map(Arc::new))
361 .collect::<HashMap<_, _>>();
362
363 let source = AtomicCancel::new();
364 source.cancel();
365 let mut written = Vec::new();
366 let err = PackFile::write_packed_from_source_to_writer_with_cancel(
367 object_ids.iter().copied(),
368 count,
369 format,
370 &PackWriteOptions::new().with_reorder(false),
371 PackWriteLimits::default(),
372 |oid| {
373 object_map
374 .get(oid)
375 .cloned()
376 .ok_or_else(|| GitError::not_found(format!("missing test object {oid}")))
377 },
378 &mut written,
379 CancelFlag::new(&source),
380 )
381 .expect_err("pre-cancelled pack write should fail");
382 assert_eq!(err, GitError::Cancelled);
383 assert!(
384 written.is_empty() || written.len() < 32,
385 "should not have finished a full pack after cancel"
386 );
387 }
388
389 fn delta_pack_options(prefer_ofs_delta: bool) -> PackWriteOptions {
390 PackWriteOptions::new()
391 .with_prefer_ofs_delta(prefer_ofs_delta)
392 .with_reorder(false)
393 }
394
395 #[test]
396 fn parses_single_blob_pack() {
397 let pack = single_object_pack(ObjectFormat::Sha1, ObjectType::Blob, b"hello\n");
398 let parsed = PackFile::parse_sha1(&pack).expect("test operation should succeed");
399 assert_eq!(parsed.version, 2);
400 assert_eq!(parsed.entries.len(), 1);
401 let object = &parsed.entries[0].object;
402 assert_eq!(object.object_type, ObjectType::Blob);
403 assert_eq!(object.body, b"hello\n");
404 assert_eq!(
405 parsed.entries[0].entry.oid.to_hex(),
406 "ce013625030ba8dba906f756967f9e9ca394464a"
407 );
408 }
409
410 #[test]
411 fn parses_single_blob_pack_sha256() {
412 let pack = single_object_pack(ObjectFormat::Sha256, ObjectType::Blob, b"hello\n");
413 let parsed =
414 PackFile::parse(&pack, ObjectFormat::Sha256).expect("test operation should succeed");
415 assert_eq!(parsed.version, 2);
416 assert_eq!(parsed.entries.len(), 1);
417 let object = &parsed.entries[0].object;
418 assert_eq!(object.object_type, ObjectType::Blob);
419 assert_eq!(object.body, b"hello\n");
420 assert_eq!(
421 parsed.entries[0].entry.oid,
422 object
423 .object_id(ObjectFormat::Sha256)
424 .expect("test operation should succeed")
425 );
426 }
427
428 #[test]
429 fn parses_bundle_pack_payload_with_bundle_format() {
430 let pack = single_object_pack(ObjectFormat::Sha1, ObjectType::Blob, b"bundle\n");
431 let oid = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"bundle\n")
432 .expect("test operation should succeed");
433 let bundle_bytes = format!("# v2 git bundle\n{oid} refs/heads/main\n\n")
434 .into_bytes()
435 .into_iter()
436 .chain(pack)
437 .collect::<Vec<_>>();
438 let bundle = Bundle::parse(&bundle_bytes, ObjectFormat::Sha1)
439 .expect("test operation should succeed");
440
441 let parsed = PackFile::parse_bundle(&bundle).expect("test operation should succeed");
442 assert_eq!(parsed.entries.len(), 1);
443 assert_eq!(parsed.entries[0].object.object_type, ObjectType::Blob);
444 assert_eq!(parsed.entries[0].object.body, b"bundle\n");
445 }
446
447 fn lying_size_blob_pack(format: ObjectFormat, declared_size: u64, real_body: &[u8]) -> Vec<u8> {
453 let mut pack = Vec::new();
454 pack.extend_from_slice(b"PACK");
455 pack.extend_from_slice(&2u32.to_be_bytes());
456 pack.extend_from_slice(&1u32.to_be_bytes());
457 write_pack_entry_header_kind(&mut pack, 3, declared_size);
459 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
460 encoder
461 .write_all(real_body)
462 .expect("test operation should succeed");
463 pack.extend_from_slice(&encoder.finish().expect("test operation should succeed"));
464 let checksum =
465 sley_core::digest_bytes(format, &pack).expect("test operation should succeed");
466 pack.extend_from_slice(checksum.as_bytes());
467 pack
468 }
469
470 #[test]
483 fn rejects_decompression_bomb_header_without_oom() {
484 for &declared in &[u64::MAX, 100 * 1024 * 1024 * 1024, u64::from(u32::MAX) * 4] {
485 let pack = lying_size_blob_pack(ObjectFormat::Sha1, declared, b"tiny\n");
486 let handle = std::thread::spawn(move || PackFile::parse_sha1(&pack));
487 let result = handle.join();
488 assert!(
490 result.is_ok(),
491 "parsing a bomb header (declared={declared}) panicked instead of erroring cleanly"
492 );
493 let parse_result = result.expect("parse thread should not panic on a bomb header");
495 assert!(
496 parse_result.is_err(),
497 "bomb header (declared={declared}) should be rejected as invalid"
498 );
499 }
500 }
501
502 fn lying_result_size_delta_pack(
509 format: ObjectFormat,
510 declared_result_size: u64,
511 delta_kind: DeltaKind,
512 ) -> Vec<u8> {
513 let base = b"hello";
514 let result = b"hello world"; let mut delta = Vec::new();
518 write_delta_varint(&mut delta, base.len() as u64);
519 write_delta_varint(&mut delta, declared_result_size);
520 let suffix = &result[base.len()..];
522 delta.push(0x90); delta.push(base.len() as u8);
524 delta.push(suffix.len() as u8);
525 delta.extend_from_slice(suffix);
526
527 let mut pack = Vec::new();
528 pack.extend_from_slice(b"PACK");
529 pack.extend_from_slice(&2u32.to_be_bytes());
530 pack.extend_from_slice(&2u32.to_be_bytes());
531
532 let base_offset = pack.len();
533 write_entry_header(&mut pack, ObjectType::Blob, base.len() as u64);
534 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
535 encoder
536 .write_all(base)
537 .expect("test operation should succeed");
538 pack.extend_from_slice(&encoder.finish().expect("test operation should succeed"));
539
540 let delta_offset = pack.len();
541 write_pack_entry_header_kind(
542 &mut pack,
543 match delta_kind {
544 DeltaKind::Offset => 6,
545 DeltaKind::Ref => 7,
546 },
547 delta.len() as u64,
548 );
549 match delta_kind {
550 DeltaKind::Offset => write_ofs_delta_offset(&mut pack, delta_offset - base_offset),
551 DeltaKind::Ref => {
552 let base_oid = sley_core::object_id_for_bytes(format, "blob", base)
553 .expect("test operation should succeed");
554 pack.extend_from_slice(base_oid.as_bytes());
555 }
556 }
557 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
558 encoder
559 .write_all(&delta)
560 .expect("test operation should succeed");
561 pack.extend_from_slice(&encoder.finish().expect("test operation should succeed"));
562
563 let checksum =
564 sley_core::digest_bytes(format, &pack).expect("test operation should succeed");
565 pack.extend_from_slice(checksum.as_bytes());
566 pack
567 }
568
569 #[test]
579 fn rejects_delta_result_size_bomb_without_oom() {
580 let bombs: &[u64] = &[u64::MAX, 1024 * 1024 * 1024 * 1024];
581 for &declared in bombs {
582 for delta_kind in [DeltaKind::Ref, DeltaKind::Offset] {
583 let pack = lying_result_size_delta_pack(ObjectFormat::Sha1, declared, delta_kind);
584 let handle = std::thread::spawn(move || PackFile::parse_sha1(&pack));
585 let join_result = handle.join();
586 assert!(
587 join_result.is_ok(),
588 "delta bomb (declared={declared}, kind={delta_kind:?}) panicked/aborted \
589 instead of erroring cleanly"
590 );
591 let parse_result =
592 join_result.expect("parse thread should not panic on a delta bomb");
593 assert!(
594 parse_result.is_err(),
595 "delta bomb (declared={declared}, kind={delta_kind:?}) should be rejected \
596 as invalid (result.len() != declared)"
597 );
598 }
599 }
600 }
601
602 #[test]
606 fn applies_legitimate_delta_after_result_size_bound() {
607 for delta_kind in [DeltaKind::Ref, DeltaKind::Offset] {
608 let base = b"hello";
609 let result = b"hello world";
610 let pack = two_object_delta_pack(ObjectFormat::Sha1, base, result, delta_kind);
611 let parsed = PackFile::parse_sha1(&pack).expect("legitimate delta should resolve");
612 assert_eq!(parsed.entries.len(), 2);
613 assert_eq!(parsed.entries[0].object.body, base);
614 assert_eq!(parsed.entries[1].object.body, result);
615 }
616 }
617
618 #[test]
619 fn bounded_delta_application_polls_cancel_between_commands() {
620 let base = vec![b'x'; 4096];
621 let mut result = base.clone();
622 result.extend_from_slice(b"changed");
623 let delta = DeltaIndex::new(&base).delta(&result).expect("delta");
624 let plan = plan_pack_delta(&base, &delta).expect("plan");
625 let mut out = Vec::with_capacity(result.len());
626 let source = AtomicCancel::new();
627 source.cancel();
628
629 assert_eq!(
630 apply_pack_delta_exact(&base, &delta, plan, &mut out, CancelFlag::new(&source)),
631 Err(GitError::Cancelled)
632 );
633 assert!(out.is_empty());
634 }
635
636 #[test]
637 fn legacy_delta_preserves_final_size_mismatch_and_copy_heavy_growth() {
638 let base = b"x";
639 let overproducing = vec![1, 1, 2, b'x', b'y'];
640 assert_eq!(
641 apply_pack_delta(base, &overproducing),
642 Err(GitError::InvalidObject(
643 "delta result size mismatch: expected 1, got 2".into()
644 ))
645 );
646
647 let copies = 4096usize;
648 let mut copy_heavy = Vec::new();
649 write_delta_varint(&mut copy_heavy, base.len() as u64);
650 write_delta_varint(&mut copy_heavy, copies as u64);
651 for _ in 0..copies {
652 copy_heavy.extend_from_slice(&[0x91, 0, 1]);
654 }
655 assert_eq!(
656 apply_pack_delta(base, ©_heavy).expect("copy-heavy legacy delta"),
657 vec![b'x'; copies]
658 );
659 }
660
661 #[test]
662 fn rejects_bundle_pack_payload_with_wrong_object_format() {
663 let pack = single_object_pack(ObjectFormat::Sha1, ObjectType::Blob, b"bundle\n");
664 let oid = sley_core::object_id_for_bytes(ObjectFormat::Sha256, "blob", b"bundle\n")
665 .expect("test operation should succeed");
666 let bundle_bytes =
667 format!("# v3 git bundle\n@object-format=sha256\n{oid} refs/heads/main\n\n")
668 .into_bytes()
669 .into_iter()
670 .chain(pack)
671 .collect::<Vec<_>>();
672 let bundle = Bundle::parse(&bundle_bytes, ObjectFormat::Sha1)
673 .expect("test operation should succeed");
674
675 assert!(PackFile::parse_bundle(&bundle).is_err());
676 }
677
678 fn assert_pack_index_view_matches_owned(index: &[u8], format: ObjectFormat) {
679 let owned = PackIndex::parse(index, format).expect("test operation should succeed");
680 let view = PackIndexView::parse(index, format).expect("test operation should succeed");
681 let owned_view =
682 PackIndexViewData::parse(Arc::from(index.to_vec().into_boxed_slice()), format)
683 .expect("test operation should succeed");
684
685 assert_eq!(view.version, owned.version);
686 assert_eq!(view.count, owned.entries.len());
687 assert_eq!(view.count(), owned.entries.len());
688 assert_eq!(view.fanout(), &owned.fanout);
689 assert_eq!(view.pack_checksum, owned.pack_checksum);
690 assert_eq!(view.index_checksum, owned.index_checksum);
691 assert_eq!(owned_view.version, owned.version);
692 assert_eq!(owned_view.count(), owned.entries.len());
693 assert_eq!(owned_view.fanout(), &owned.fanout);
694 assert_eq!(owned_view.pack_checksum, owned.pack_checksum);
695 assert_eq!(owned_view.index_checksum, owned.index_checksum);
696 for entry in &owned.entries {
697 let owned_found = owned
698 .find(&entry.oid)
699 .expect("test operation should succeed");
700 let expected = Some(PackIndexLookup {
701 crc32: owned_found.crc32,
702 offset: owned_found.offset,
703 });
704 assert_eq!(view.find(&entry.oid), expected);
705 assert_eq!(owned_view.find(&entry.oid), expected);
706 }
707 }
708
709 #[test]
710 fn writes_pack_and_index_that_round_trip() {
711 let object = EncodedObject::new(ObjectType::Blob, b"hello\n".to_vec());
712 let written = PackFile::write_undeltified_sha1(std::slice::from_ref(&object))
713 .expect("test operation should succeed");
714 let pack = PackFile::parse_sha1(&written.pack).expect("test operation should succeed");
715 let index =
716 PackIndex::parse_v2_sha1(&written.index).expect("test operation should succeed");
717 let oid = object
718 .object_id(ObjectFormat::Sha1)
719 .expect("test operation should succeed");
720 assert_eq!(pack.entries[0].object, object);
721 assert_eq!(index.pack_checksum, pack.checksum);
722 assert_eq!(
723 index
724 .find(&oid)
725 .expect("test operation should succeed")
726 .offset,
727 12
728 );
729 }
730
731 #[test]
732 fn pack_index_view_matches_owned_index_for_generated_sha1_pack() {
733 let objects = (0..8)
734 .map(|idx| {
735 EncodedObject::new(
736 ObjectType::Blob,
737 format!("borrowed pack index view sha1 object {idx}\n").into_bytes(),
738 )
739 })
740 .collect::<Vec<_>>();
741 let written = PackFile::write_packed(&objects, ObjectFormat::Sha1)
742 .expect("test operation should succeed");
743
744 assert_pack_index_view_matches_owned(&written.index, ObjectFormat::Sha1);
745
746 let view =
747 PackIndexView::parse_v2_sha1(&written.index).expect("test operation should succeed");
748 let missing = sley_core::object_id_for_bytes(
749 ObjectFormat::Sha1,
750 "blob",
751 b"not present in borrowed index\n",
752 )
753 .expect("test operation should succeed");
754 assert_eq!(view.find(&missing), None);
755 }
756
757 #[test]
758 fn writes_sha256_pack_and_index_that_round_trip() {
759 let object = EncodedObject::new(ObjectType::Blob, b"hello sha256\n".to_vec());
760 let written =
761 PackFile::write_undeltified(std::slice::from_ref(&object), ObjectFormat::Sha256)
762 .expect("test operation should succeed");
763 let pack = PackFile::parse(&written.pack, ObjectFormat::Sha256)
764 .expect("test operation should succeed");
765 let index = PackIndex::parse(&written.index, ObjectFormat::Sha256)
766 .expect("test operation should succeed");
767 let oid = object
768 .object_id(ObjectFormat::Sha256)
769 .expect("test operation should succeed");
770 assert_eq!(pack.entries[0].object, object);
771 assert_eq!(index.pack_checksum, pack.checksum);
772 assert_eq!(index.pack_checksum.format(), ObjectFormat::Sha256);
773 assert_eq!(index.index_checksum.format(), ObjectFormat::Sha256);
774 assert_eq!(
775 index
776 .find(&oid)
777 .expect("test operation should succeed")
778 .offset,
779 12
780 );
781 }
782
783 #[test]
784 fn pack_index_view_matches_owned_index_for_generated_sha256_pack() {
785 let objects = (0..4)
786 .map(|idx| {
787 EncodedObject::new(
788 ObjectType::Blob,
789 format!("borrowed pack index view sha256 object {idx}\n").into_bytes(),
790 )
791 })
792 .collect::<Vec<_>>();
793 let written = PackFile::write_undeltified(&objects, ObjectFormat::Sha256)
794 .expect("test operation should succeed");
795
796 assert_pack_index_view_matches_owned(&written.index, ObjectFormat::Sha256);
797 }
798
799 #[test]
800 fn indexes_existing_sha256_pack_bytes() {
801 let object = EncodedObject::new(ObjectType::Blob, b"index raw sha256 pack\n".to_vec());
802 let written =
803 PackFile::write_undeltified(std::slice::from_ref(&object), ObjectFormat::Sha256)
804 .expect("test operation should succeed");
805
806 let indexed = PackIndex::write_v2_for_pack(&written.pack, ObjectFormat::Sha256)
807 .expect("test operation should succeed");
808 let index = PackIndex::parse(&indexed.index, ObjectFormat::Sha256)
809 .expect("test operation should succeed");
810
811 assert_eq!(indexed.pack_checksum, written.checksum);
812 assert_eq!(indexed.entries, written.entries);
813 assert_eq!(index.pack_checksum, written.checksum);
814 assert_eq!(index.entries, written.entries);
815 }
816
817 #[test]
818 fn indexes_existing_delta_pack_bytes() {
819 let (base, changed) = similar_blob_objects();
820 let options = delta_pack_options(true);
821 let written = PackFile::write_packed_with_options(
822 &[base, changed.clone()],
823 ObjectFormat::Sha1,
824 &options,
825 )
826 .expect("test operation should succeed");
827
828 let indexed = PackIndex::write_v2_for_pack(&written.pack, ObjectFormat::Sha1)
829 .expect("test operation should succeed");
830 let index =
831 PackIndex::parse_v2_sha1(&indexed.index).expect("test operation should succeed");
832 let changed_oid = changed
833 .object_id(ObjectFormat::Sha1)
834 .expect("test operation should succeed");
835
836 assert_eq!(indexed.pack_checksum, written.checksum);
837 assert_eq!(indexed.entries, written.entries);
838 assert_eq!(
839 index
840 .find(&changed_oid)
841 .expect("test operation should succeed")
842 .offset,
843 written.entries[1].offset
844 );
845 assert_eq!(
846 index
847 .find(&changed_oid)
848 .expect("test operation should succeed")
849 .crc32,
850 written.entries[1].crc32
851 );
852 }
853
854 #[test]
855 fn git_generated_pack_is_identical_with_one_and_many_workers() {
856 let root = unique_temp_dir("parallel-git-pack");
857 let repository = root.join("repo");
858 fs::create_dir_all(&repository).expect("create git fixture directory");
859 run_git_success(&repository, &["init", "-q"]);
860 run_git_success(&repository, &["config", "user.name", "Sley Test"]);
861 run_git_success(
862 &repository,
863 &["config", "user.email", "sley@example.invalid"],
864 );
865 for index in 0..64 {
866 let body = format!("git generated pack object {index}\n").repeat(128);
867 fs::write(repository.join(format!("object-{index:03}.txt")), body)
868 .expect("write git fixture object");
869 }
870 run_git_success(&repository, &["add", "."]);
871 run_git_success(&repository, &["commit", "-q", "-m", "pack fixture"]);
872 for index in 0..64 {
873 let path = repository.join(format!("object-{index:03}.txt"));
874 let mut body = fs::read(&path).expect("read git fixture object");
875 body.extend_from_slice(format!("second version {index}\n").as_bytes());
876 fs::write(path, body).expect("update git fixture object");
877 }
878 run_git_success(&repository, &["add", "."]);
879 run_git_success(&repository, &["commit", "-q", "-m", "deltified fixture"]);
880 run_git_success(&repository, &["gc", "--aggressive", "--prune=now"]);
881
882 let pack_dir = repository.join(".git/objects/pack");
883 let pack_path = largest_path_with_extension(&pack_dir, "pack");
884 let pack = fs::read(&pack_path).expect("read git-generated pack");
885 let git_index = fs::read(pack_path.with_extension("idx")).expect("read git index");
886 let one = PackIndex::write_v2_for_pack_with_options(
887 &pack,
888 ObjectFormat::Sha1,
889 |_| Ok(None),
890 PackIndexOptions::default().with_threads(1),
891 CancelFlag::never(),
892 |_| {},
893 )
894 .expect("index git pack with one worker");
895 let many = PackIndex::write_v2_for_pack_with_options(
896 &pack,
897 ObjectFormat::Sha1,
898 |_| Ok(None),
899 PackIndexOptions::default().with_threads(64),
900 CancelFlag::never(),
901 |_| {},
902 )
903 .expect("index git pack with many workers");
904 assert_eq!(many, one);
905 assert_eq!(many.index, git_index);
906 fs::remove_dir_all(root).expect("remove git fixture");
907 }
908
909 #[test]
910 fn parallel_index_rejects_self_referential_ref_delta_with_valid_checksum() {
911 let format = ObjectFormat::Sha1;
912 let mut pack = ref_delta_chain_pack(format, 1, false);
913 let descriptors = pack_entry_descriptors(&pack, format);
914 let delta_offset = descriptors[1].offset as usize;
915 let mut base_oid_offset = delta_offset;
916 let header = parse_entry_header(&pack, &mut base_oid_offset).expect("delta header");
917 assert_eq!(header.kind, PackObjectKind::RefDelta);
918 let self_oid = sley_core::object_id_for_bytes(format, "blob", &chain_bodies(1)[1])
919 .expect("self object id");
920 let base_oid_end = base_oid_offset + format.raw_len();
921 pack[base_oid_offset..base_oid_end].copy_from_slice(self_oid.as_bytes());
922 let trailer_offset = pack.len() - format.raw_len();
923 let checksum = sley_core::digest_bytes(format, &pack[..trailer_offset])
924 .expect("recompute valid pack checksum");
925 pack[trailer_offset..].copy_from_slice(checksum.as_bytes());
926
927 let error = PackIndex::write_v2_for_pack_with_options(
928 &pack,
929 format,
930 |_| Ok(None),
931 PackIndexOptions::default().with_threads(32),
932 CancelFlag::never(),
933 |_| {},
934 )
935 .expect_err("a self-referential delta chain must be rejected");
936 assert!(
937 error.to_string().contains("cyclic") || error.to_string().contains("unresolved"),
938 "unexpected chain error: {error}"
939 );
940 }
941
942 #[test]
943 fn writes_ref_delta_pack_and_index_that_round_trip() {
944 let (base, changed) = similar_blob_objects();
945 let options = delta_pack_options(false);
946 let written = PackFile::write_packed_with_options(
947 &[base.clone(), changed.clone()],
948 ObjectFormat::Sha1,
949 &options,
950 )
951 .expect("test operation should succeed");
952 let mut second_offset = written.entries[1].offset as usize;
953 let header = parse_entry_header(&written.pack, &mut second_offset)
954 .expect("test operation should succeed");
955 assert_eq!(header.kind, PackObjectKind::RefDelta);
956
957 let pack = PackFile::parse_sha1(&written.pack).expect("test operation should succeed");
958 let index =
959 PackIndex::parse_v2_sha1(&written.index).expect("test operation should succeed");
960 let oid = changed
961 .object_id(ObjectFormat::Sha1)
962 .expect("test operation should succeed");
963 assert_eq!(pack.entries[0].object, base);
964 assert_eq!(pack.entries[1].object, changed);
965 assert_eq!(index.pack_checksum, pack.checksum);
966 assert_eq!(
967 index
968 .find(&oid)
969 .expect("test operation should succeed")
970 .offset,
971 written.entries[1].offset
972 );
973 }
974
975 #[test]
976 fn read_object_at_matches_full_parse_for_ofs_delta_pack() {
977 let (base, changed) = similar_blob_objects();
978 let options = delta_pack_options(true);
979 let written =
980 PackFile::write_packed_with_options(&[base, changed], ObjectFormat::Sha1, &options)
981 .expect("test operation should succeed");
982 let mut second = written.entries[1].offset as usize;
984 assert_eq!(
985 parse_entry_header(&written.pack, &mut second)
986 .expect("test operation should succeed")
987 .kind,
988 PackObjectKind::OfsDelta
989 );
990 let parsed = PackFile::parse_sha1(&written.pack).expect("test operation should succeed");
992 for po in &parsed.entries {
993 let got =
994 read_object_at_arc(&written.pack, po.entry.offset, ObjectFormat::Sha1, |_| {
995 Ok(None)
996 })
997 .expect("test operation should succeed");
998 assert_eq!(*got, po.object, "offset {}", po.entry.offset);
999 }
1000 }
1001
1002 #[derive(Default)]
1005 struct MapHeaderTypeCache(HashMap<u64, PackObjectHeader>);
1006
1007 impl HeaderTypeCache for MapHeaderTypeCache {
1008 fn get(&self, pack_offset: u64) -> Option<PackObjectHeader> {
1009 self.0.get(&pack_offset).copied()
1010 }
1011 fn put(&mut self, pack_offset: u64, header: PackObjectHeader) {
1012 self.0.insert(pack_offset, header);
1013 }
1014 }
1015
1016 #[test]
1017 fn read_object_header_at_cached_matches_uncached_cold_and_warm_for_ofs_delta() {
1018 let (base, changed) = similar_blob_objects();
1019 let options = delta_pack_options(true);
1020 let written =
1021 PackFile::write_packed_with_options(&[base, changed], ObjectFormat::Sha1, &options)
1022 .expect("test operation should succeed");
1023 let mut second = written.entries[1].offset as usize;
1025 assert_eq!(
1026 parse_entry_header(&written.pack, &mut second)
1027 .expect("test operation should succeed")
1028 .kind,
1029 PackObjectKind::OfsDelta
1030 );
1031
1032 let parsed = PackFile::parse_sha1(&written.pack).expect("test operation should succeed");
1033 let mut cache = MapHeaderTypeCache::default();
1034 for po in &parsed.entries {
1035 let uncached = read_object_header_at(
1036 &written.pack,
1037 po.entry.offset,
1038 ObjectFormat::Sha1,
1039 0,
1040 |_, _| Ok(None),
1041 )
1042 .expect("test operation should succeed");
1043 assert_eq!(
1045 uncached.type_and_size(),
1046 (po.object.object_type, po.object.body.len() as u64),
1047 "uncached header at offset {}",
1048 po.entry.offset
1049 );
1050 let cold = read_object_header_at_with_cache(
1052 &written.pack,
1053 po.entry.offset,
1054 ObjectFormat::Sha1,
1055 0,
1056 |_, _| Ok(None),
1057 &mut cache,
1058 )
1059 .expect("test operation should succeed");
1060 assert_eq!(cold, uncached, "cold cache at offset {}", po.entry.offset);
1061 }
1062 for po in &parsed.entries {
1065 let warm = read_object_header_at_with_cache(
1066 &written.pack,
1067 po.entry.offset,
1068 ObjectFormat::Sha1,
1069 0,
1070 |_, _| panic!("warm cache must not re-walk the chain"),
1071 &mut cache,
1072 )
1073 .expect("test operation should succeed");
1074 assert_eq!(
1075 warm.type_and_size(),
1076 (po.object.object_type, po.object.body.len() as u64),
1077 "warm cache at offset {}",
1078 po.entry.offset
1079 );
1080 }
1081 }
1082
1083 #[test]
1084 fn read_object_at_matches_full_parse_for_ref_delta_pack() {
1085 let (base, changed) = similar_blob_objects();
1086 let options = delta_pack_options(false);
1087 let written =
1088 PackFile::write_packed_with_options(&[base, changed], ObjectFormat::Sha1, &options)
1089 .expect("test operation should succeed");
1090 let parsed = PackFile::parse_sha1(&written.pack).expect("test operation should succeed");
1091 let by_oid: HashMap<ObjectId, Arc<EncodedObject>> = parsed
1092 .entries
1093 .iter()
1094 .map(|po| (po.entry.oid, Arc::new(po.object.clone())))
1095 .collect();
1096 for po in &parsed.entries {
1097 let got =
1098 read_object_at_arc(&written.pack, po.entry.offset, ObjectFormat::Sha1, |oid| {
1099 Ok(by_oid.get(oid).cloned())
1100 })
1101 .expect("test operation should succeed");
1102 assert_eq!(*got, po.object);
1103 }
1104 }
1105
1106 #[derive(Default)]
1110 struct CountingDeltaCache {
1111 map: std::cell::RefCell<HashMap<u64, Arc<EncodedObject>>>,
1112 hits: std::cell::Cell<usize>,
1113 inserts: std::cell::Cell<usize>,
1114 }
1115
1116 impl PackDeltaCache for CountingDeltaCache {
1117 fn get(&self, offset: u64) -> Option<Arc<EncodedObject>> {
1118 let hit = self.map.borrow().get(&offset).cloned();
1119 if hit.is_some() {
1120 self.hits.set(self.hits.get() + 1);
1121 }
1122 hit
1123 }
1124 fn insert(&self, offset: u64, object: Arc<EncodedObject>) {
1125 self.inserts.set(self.inserts.get() + 1);
1126 self.map.borrow_mut().insert(offset, object);
1127 }
1128 }
1129
1130 #[test]
1131 fn read_object_at_with_cache_matches_uncached_and_reuses_bases() {
1132 let mut objects = Vec::new();
1135 for idx in 0..8u32 {
1136 let mut body = vec![b'x'; 4096];
1137 body.extend_from_slice(format!("\nvariant {idx}\n").as_bytes());
1138 objects.push(EncodedObject::new(ObjectType::Blob, body));
1139 }
1140 let options = delta_pack_options(true);
1141 let written = PackFile::write_packed_with_options(&objects, ObjectFormat::Sha1, &options)
1142 .expect("test operation should succeed");
1143 let parsed = PackFile::parse_sha1(&written.pack).expect("test operation should succeed");
1144
1145 let cache = CountingDeltaCache::default();
1146 for _ in 0..2 {
1149 for po in &parsed.entries {
1150 let got = read_object_at_with_cache_arc(
1151 &written.pack,
1152 po.entry.offset,
1153 ObjectFormat::Sha1,
1154 |_| Ok(None),
1155 &cache,
1156 )
1157 .expect("test operation should succeed");
1158 assert_eq!(*got, po.object, "offset {}", po.entry.offset);
1159 }
1160 }
1161 assert!(cache.hits.get() > 0, "cache never served a warm object");
1164 }
1165
1166 #[test]
1167 fn writes_ofs_delta_pack_and_index_that_round_trip() {
1168 let (base, changed) = similar_blob_objects();
1169 let options = delta_pack_options(true);
1170 let written = PackFile::write_packed_with_options(
1171 &[base.clone(), changed.clone()],
1172 ObjectFormat::Sha1,
1173 &options,
1174 )
1175 .expect("test operation should succeed");
1176 let mut second_offset = written.entries[1].offset as usize;
1177 let header = parse_entry_header(&written.pack, &mut second_offset)
1178 .expect("test operation should succeed");
1179 assert_eq!(header.kind, PackObjectKind::OfsDelta);
1180
1181 let pack = PackFile::parse_sha1(&written.pack).expect("test operation should succeed");
1182 let index =
1183 PackIndex::parse_v2_sha1(&written.index).expect("test operation should succeed");
1184 let oid = changed
1185 .object_id(ObjectFormat::Sha1)
1186 .expect("test operation should succeed");
1187 assert_eq!(pack.entries[0].object, base);
1188 assert_eq!(pack.entries[1].object, changed);
1189 assert_eq!(index.pack_checksum, pack.checksum);
1190 assert_eq!(
1191 index
1192 .find(&oid)
1193 .expect("test operation should succeed")
1194 .offset,
1195 written.entries[1].offset
1196 );
1197 }
1198
1199 #[test]
1200 fn resolves_ofs_delta_pack_entry() {
1201 let base = b"hello";
1202 let result = b"hello world";
1203 let pack = two_object_delta_pack(ObjectFormat::Sha1, base, result, DeltaKind::Offset);
1204 let parsed = PackFile::parse_sha1(&pack).expect("test operation should succeed");
1205 assert_eq!(parsed.entries.len(), 2);
1206 assert_eq!(parsed.entries[0].object.body, base);
1207 assert_eq!(parsed.entries[1].object.body, result);
1208 assert_eq!(
1209 parsed.entries[1].entry.oid,
1210 sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", result)
1211 .expect("test operation should succeed")
1212 );
1213 }
1214
1215 #[test]
1216 fn resolves_ref_delta_pack_entry() {
1217 let base = b"hello";
1218 let result = b"hello world";
1219 let pack = two_object_delta_pack(ObjectFormat::Sha1, base, result, DeltaKind::Ref);
1220 let parsed = PackFile::parse_sha1(&pack).expect("test operation should succeed");
1221 assert_eq!(parsed.entries.len(), 2);
1222 assert_eq!(parsed.entries[0].object.body, base);
1223 assert_eq!(parsed.entries[1].object.body, result);
1224 assert_eq!(
1225 parsed.entries[1].entry.oid,
1226 sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", result)
1227 .expect("test operation should succeed")
1228 );
1229 }
1230
1231 #[test]
1232 fn resolves_thin_ref_delta_pack_entry_with_external_base() {
1233 let base = b"hello";
1234 let result = b"hello world";
1235 let pack = thin_ref_delta_pack(ObjectFormat::Sha1, base, result);
1236 assert!(PackFile::parse_sha1(&pack).is_err());
1237
1238 let base_oid = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", base)
1239 .expect("test operation should succeed");
1240 let parsed = PackFile::parse_thin(&pack, ObjectFormat::Sha1, |oid| {
1241 if oid == &base_oid {
1242 Ok(Some(EncodedObject::new(ObjectType::Blob, base.to_vec())))
1243 } else {
1244 Ok(None)
1245 }
1246 })
1247 .expect("test operation should succeed");
1248 assert_eq!(parsed.entries.len(), 1);
1249 assert_eq!(parsed.entries[0].object.body, result);
1250 assert_eq!(
1251 parsed.entries[0].entry.oid,
1252 sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", result)
1253 .expect("test operation should succeed")
1254 );
1255 }
1256
1257 #[test]
1258 fn fixes_thin_pack_and_indexes_it_without_external_bases() {
1259 for format in [ObjectFormat::Sha1, ObjectFormat::Sha256] {
1260 let base = b"hello";
1261 let result = b"hello world";
1262 let pack = thin_ref_delta_pack(format, base, result);
1263 let base_object = EncodedObject::new(ObjectType::Blob, base.to_vec());
1264 let base_oid = base_object
1265 .object_id(format)
1266 .expect("test operation should succeed");
1267
1268 let fixed = fix_thin_pack(&pack, format, |oid| {
1269 Ok((oid == &base_oid).then(|| base_object.clone()))
1270 })
1271 .expect("thin pack completion should succeed");
1272
1273 assert_eq!(u32_be(&fixed.pack[8..12]), 2);
1274 assert_eq!(fixed.appended_bases, vec![base_oid]);
1275 assert_eq!(fixed.index.entries.len(), 2);
1276 let parsed =
1277 PackFile::parse(&fixed.pack, format).expect("completed pack should stand alone");
1278 assert_eq!(parsed.entries.len(), 2);
1279 assert_eq!(parsed.entries[0].object.body, result);
1280 assert_eq!(parsed.entries[1].object, base_object);
1281 let rebuilt = PackIndex::write_v2_for_pack(&fixed.pack, format)
1282 .expect("completed pack should index without a resolver");
1283 assert_eq!(rebuilt, fixed.index);
1284 }
1285 }
1286
1287 #[test]
1288 fn fix_thin_is_idempotent_for_a_complete_pack() {
1289 let original = single_object_pack(ObjectFormat::Sha1, ObjectType::Blob, b"complete\n");
1290 let mut resolver_called = false;
1291 let fixed = fix_thin_pack(&original, ObjectFormat::Sha1, |_| {
1292 resolver_called = true;
1293 Ok(None)
1294 })
1295 .expect("complete pack should remain complete");
1296
1297 assert_eq!(fixed.pack, original);
1298 assert!(fixed.appended_bases.is_empty());
1299 assert!(!resolver_called);
1300 assert_eq!(fixed.index.entries.len(), 1);
1301 }
1302
1303 #[test]
1304 fn fix_thin_does_not_duplicate_a_base_already_in_the_body() {
1305 let pack =
1306 two_object_delta_pack(ObjectFormat::Sha1, b"hello", b"hello world", DeltaKind::Ref);
1307 let original_count = u32_be(&pack[8..12]);
1308 let mut resolver_called = false;
1309 let fixed = fix_thin_pack(&pack, ObjectFormat::Sha1, |_| {
1310 resolver_called = true;
1311 Ok(None)
1312 })
1313 .expect("in-pack ref base should remain self-contained");
1314
1315 assert_eq!(fixed.pack, pack);
1316 assert_eq!(u32_be(&fixed.pack[8..12]), original_count);
1317 assert_eq!(fixed.index.entries.len(), original_count as usize);
1318 assert!(fixed.appended_bases.is_empty());
1319 assert!(!resolver_called);
1320 }
1321
1322 #[test]
1323 fn fix_thin_appends_a_repeated_external_base_once() {
1324 let base = b"shared";
1325 let pack =
1326 thin_ref_delta_pack_many(ObjectFormat::Sha1, base, &[b"shared one", b"shared two"]);
1327 let base_object = EncodedObject::new(ObjectType::Blob, base.to_vec());
1328 let base_oid = base_object
1329 .object_id(ObjectFormat::Sha1)
1330 .expect("test operation should succeed");
1331 let mut calls = 0;
1332 let fixed = fix_thin_pack(&pack, ObjectFormat::Sha1, |oid| {
1333 calls += 1;
1334 Ok((oid == &base_oid).then(|| base_object.clone()))
1335 })
1336 .expect("shared base completion should succeed");
1337
1338 assert_eq!(calls, 1);
1339 assert_eq!(fixed.appended_bases, vec![base_oid]);
1340 assert_eq!(u32_be(&fixed.pack[8..12]), 3);
1341 assert_eq!(fixed.index.entries.len(), 3);
1342 }
1343
1344 #[test]
1345 fn fix_thin_rejects_missing_and_wrong_external_bases() {
1346 let base = b"hello";
1347 let pack = thin_ref_delta_pack(ObjectFormat::Sha1, base, b"hello world");
1348 assert!(fix_thin_pack(&pack, ObjectFormat::Sha1, |_| Ok(None)).is_err());
1349
1350 let wrong = EncodedObject::new(ObjectType::Blob, b"wrong".to_vec());
1351 let error = fix_thin_pack(&pack, ObjectFormat::Sha1, |_| Ok(Some(wrong.clone())))
1352 .expect_err("wrong base body must not be accepted for the requested oid");
1353 assert!(error.to_string().contains("resolved to object"));
1354 }
1355
1356 #[test]
1357 fn fix_thin_preserves_the_delta_depth_limit_boundary() {
1358 let base = b"hello";
1359 let pack = thin_ref_delta_pack(ObjectFormat::Sha1, base, b"hello world");
1360 let base_object = EncodedObject::new(ObjectType::Blob, base.to_vec());
1361 let base_oid = base_object
1362 .object_id(ObjectFormat::Sha1)
1363 .expect("test operation should succeed");
1364 let resolve = |oid: &ObjectId| Ok((oid == &base_oid).then(|| base_object.clone()));
1365
1366 fix_thin_pack_with_limits(
1367 &pack,
1368 ObjectFormat::Sha1,
1369 resolve,
1370 PackReadLimits {
1371 max_delta_depth: 1,
1372 ..PackReadLimits::default()
1373 },
1374 )
1375 .expect("one delta must pass at the exact limit");
1376
1377 let error = fix_thin_pack_with_limits(
1378 &pack,
1379 ObjectFormat::Sha1,
1380 resolve,
1381 PackReadLimits {
1382 max_delta_depth: 0,
1383 ..PackReadLimits::default()
1384 },
1385 )
1386 .expect_err("one delta must fail immediately above the configured limit");
1387 assert!(error.to_string().contains("observed depth 1"));
1388 }
1389
1390 #[test]
1391 fn parallel_indexer_resolves_thin_bases_but_rejects_missing_ones() {
1392 let base = b"hello";
1393 let result = b"hello stream";
1394 let pack = thin_ref_delta_pack(ObjectFormat::Sha1, base, result);
1395 let base_object = EncodedObject::new(ObjectType::Blob, base.to_vec());
1396 let base_oid = base_object
1397 .object_id(ObjectFormat::Sha1)
1398 .expect("test operation should succeed");
1399
1400 let built = PackIndex::write_v2_for_pack_with_base(&pack, ObjectFormat::Sha1, |oid| {
1401 Ok((oid == &base_oid).then(|| base_object.clone()))
1402 })
1403 .expect("parallel indexer should resolve the supplied base");
1404 assert_eq!(built.entries.len(), 1);
1405 assert_eq!(
1406 built.entries[0].oid,
1407 sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", result)
1408 .expect("test operation should succeed")
1409 );
1410
1411 let error = PackIndex::write_v2_for_pack_with_base(&pack, ObjectFormat::Sha1, |_| Ok(None))
1412 .expect_err("an unresolved base must remain an error");
1413 assert!(error.to_string().contains("unresolved"));
1414 }
1415
1416 #[test]
1417 fn fixed_pack_passes_upstream_git_and_supports_clone() {
1418 let format = ObjectFormat::Sha1;
1419 let base = EncodedObject::new(
1420 ObjectType::Blob,
1421 b"the original contents are deliberately similar\n".repeat(32),
1422 );
1423 let mut target_body = base.body.clone();
1424 target_body.extend_from_slice(b"completed thin pack\n");
1425 let target = EncodedObject::new(ObjectType::Blob, target_body);
1426 let target_oid = target
1427 .object_id(format)
1428 .expect("test operation should succeed");
1429
1430 let mut tree_body = b"100644 payload\0".to_vec();
1431 tree_body.extend_from_slice(target_oid.as_bytes());
1432 let tree = EncodedObject::new(ObjectType::Tree, tree_body);
1433 let tree_oid = tree
1434 .object_id(format)
1435 .expect("test operation should succeed");
1436 let commit = EncodedObject::new(
1437 ObjectType::Commit,
1438 format!(
1439 "tree {tree_oid}\nauthor Sley <sley@example.com> 1 +0000\n\
1440 committer Sley <sley@example.com> 1 +0000\n\nthin pack fixture\n"
1441 )
1442 .into_bytes(),
1443 );
1444 let commit_oid = commit
1445 .object_id(format)
1446 .expect("test operation should succeed");
1447 let base_oid = base
1448 .object_id(format)
1449 .expect("test operation should succeed");
1450 let thin = PackFile::write_thin(
1451 &[commit, tree, target],
1452 format,
1453 HashMap::from([(base_oid, base.clone())]),
1454 )
1455 .expect("write thin pack");
1456 assert!(thin.delta_count > 0, "fixture must really be thin");
1457 assert!(PackIndex::write_v2_for_pack(&thin.pack, format).is_err());
1458
1459 let fixed = fix_thin_pack(&thin.pack, format, |oid| {
1460 Ok((oid == &base_oid).then(|| base.clone()))
1461 })
1462 .expect("fix thin pack");
1463 let root = unique_temp_dir("fix-thin-git");
1464 let strict_pack = root.join("strict.pack");
1465 fs::create_dir_all(&root).expect("create test root");
1466 fs::write(&strict_pack, &fixed.pack).expect("write strict pack");
1467 run_git_success(&root, &["index-pack", "--strict", "strict.pack"]);
1468
1469 let complete_repo = root.join("complete.git");
1470 run_git_success(
1471 &root,
1472 &[
1473 "init",
1474 "--bare",
1475 "-q",
1476 complete_repo.to_str().expect("utf8 complete repo path"),
1477 ],
1478 );
1479 let complete_pack_dir = complete_repo.join("objects/pack");
1480 let complete_stem = format!("pack-{}", fixed.index.pack_checksum);
1481 fs::write(
1482 complete_pack_dir.join(format!("{complete_stem}.pack")),
1483 &fixed.pack,
1484 )
1485 .expect("write completed repository pack");
1486 fs::write(
1487 complete_pack_dir.join(format!("{complete_stem}.idx")),
1488 &fixed.index.index,
1489 )
1490 .expect("write completed repository index");
1491 fs::create_dir_all(complete_repo.join("refs/heads")).expect("create refs");
1492 fs::write(
1493 complete_repo.join("refs/heads/main"),
1494 format!("{commit_oid}\n"),
1495 )
1496 .expect("write main ref");
1497 fs::write(complete_repo.join("HEAD"), b"ref: refs/heads/main\n").expect("write HEAD");
1498 run_git_success(&complete_repo, &["fsck", "--full"]);
1499
1500 let clone_path = root.join("clone");
1501 run_git_success(
1502 &root,
1503 &[
1504 "clone",
1505 "--no-local",
1506 "-q",
1507 complete_repo.to_str().expect("utf8 complete repo path"),
1508 clone_path.to_str().expect("utf8 clone path"),
1509 ],
1510 );
1511 run_git_success(&clone_path, &["fsck", "--full"]);
1512
1513 let thin_repo = root.join("thin.git");
1514 run_git_success(
1515 &root,
1516 &[
1517 "init",
1518 "--bare",
1519 "-q",
1520 thin_repo.to_str().expect("utf8 thin repo path"),
1521 ],
1522 );
1523 let thin_index = PackIndex::write_v2_for_pack_with_base(&thin.pack, format, |oid| {
1524 Ok((oid == &base_oid).then(|| base.clone()))
1525 })
1526 .expect("sley can build the deliberately permissive thin index");
1527 let thin_pack_dir = thin_repo.join("objects/pack");
1528 let thin_stem = format!("pack-{}", thin_index.pack_checksum);
1529 fs::write(thin_pack_dir.join(format!("{thin_stem}.pack")), &thin.pack)
1530 .expect("write thin repository pack");
1531 fs::write(
1532 thin_pack_dir.join(format!("{thin_stem}.idx")),
1533 &thin_index.index,
1534 )
1535 .expect("write thin repository index");
1536 fs::create_dir_all(thin_repo.join("refs/heads")).expect("create thin refs");
1537 fs::write(thin_repo.join("refs/heads/main"), format!("{commit_oid}\n"))
1538 .expect("write thin main ref");
1539 fs::write(thin_repo.join("HEAD"), b"ref: refs/heads/main\n").expect("write thin HEAD");
1540 let broken_clone_path = root.join("broken-clone");
1541 let failed_clone = Command::new("git")
1542 .current_dir(&root)
1543 .args([
1544 "clone",
1545 "--no-local",
1546 "-q",
1547 thin_repo.to_str().expect("utf8 thin repo path"),
1548 broken_clone_path.to_str().expect("utf8 broken clone path"),
1549 ])
1550 .output()
1551 .expect("run failing clone");
1552 assert!(
1553 !failed_clone.status.success(),
1554 "clone from uncompleted thin pack unexpectedly succeeded"
1555 );
1556
1557 fs::remove_dir_all(root).expect("remove git interoperability fixture");
1558 }
1559
1560 #[test]
1561 fn rejects_bad_pack_checksum() {
1562 let mut pack = single_object_pack(ObjectFormat::Sha1, ObjectType::Blob, b"hello\n");
1563 let last = pack.len() - 1;
1564 pack[last] ^= 1;
1565 assert!(PackFile::parse_sha1(&pack).is_err());
1566 }
1567
1568 #[test]
1569 fn raw_pack_index_rejects_bad_pack_checksum() {
1570 let mut pack = single_object_pack(ObjectFormat::Sha1, ObjectType::Blob, b"hello\n");
1571 let last = pack.len() - 1;
1572 pack[last] ^= 1;
1573 assert!(PackIndex::write_v2_for_pack(&pack, ObjectFormat::Sha1).is_err());
1574 }
1575
1576 #[test]
1577 fn pack_index_writer_preserves_duplicate_object_ids() {
1578 let oid = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"same\n")
1579 .expect("test operation should succeed");
1580 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1581 .expect("test operation should succeed");
1582 let entries = vec![
1583 PackIndexEntry {
1584 oid,
1585 crc32: 1,
1586 offset: 12,
1587 },
1588 PackIndexEntry {
1589 oid,
1590 crc32: 2,
1591 offset: 24,
1592 },
1593 ];
1594 let index = PackIndex::write_v2(ObjectFormat::Sha1, &entries, &pack_checksum)
1595 .expect("duplicate objects are valid in a pack index");
1596 let parsed = PackIndex::parse(&index, ObjectFormat::Sha1)
1597 .expect("duplicate-object pack index should parse");
1598 assert_eq!(parsed.entries.len(), 2);
1599 assert!(parsed.entries.iter().all(|entry| entry.oid == oid));
1600 assert!(parsed.find(&oid).is_some());
1601 }
1602
1603 #[test]
1604 fn parses_single_entry_pack_index() {
1605 let oid = ObjectId::from_hex(
1606 ObjectFormat::Sha1,
1607 "ce013625030ba8dba906f756967f9e9ca394464a",
1608 )
1609 .expect("test operation should succeed");
1610 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1611 .expect("test operation should succeed");
1612 let index = single_entry_index(
1613 ObjectFormat::Sha1,
1614 oid,
1615 0x1234_5678,
1616 12,
1617 pack_checksum.clone(),
1618 );
1619 let parsed = PackIndex::parse_v2_sha1(&index).expect("test operation should succeed");
1620 assert_eq!(parsed.version, 2);
1621 assert_eq!(parsed.pack_checksum, pack_checksum);
1622 assert_eq!(parsed.entries.len(), 1);
1623 assert_eq!(
1624 parsed
1625 .find(&oid)
1626 .expect("test operation should succeed")
1627 .offset,
1628 12
1629 );
1630 assert_eq!(
1631 parsed
1632 .find(&oid)
1633 .expect("test operation should succeed")
1634 .crc32,
1635 0x1234_5678
1636 );
1637 assert_pack_index_view_matches_owned(&index, ObjectFormat::Sha1);
1638 }
1639
1640 #[test]
1641 fn parses_single_entry_pack_index_v1() {
1642 let oid = ObjectId::from_hex(
1643 ObjectFormat::Sha1,
1644 "ce013625030ba8dba906f756967f9e9ca394464a",
1645 )
1646 .expect("test operation should succeed");
1647 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1648 .expect("test operation should succeed");
1649 let index =
1650 single_entry_index_v1(ObjectFormat::Sha1, oid, 0x1234_5678, pack_checksum.clone());
1651 let parsed =
1652 PackIndex::parse(&index, ObjectFormat::Sha1).expect("test operation should succeed");
1653 assert_eq!(parsed.version, 1);
1654 assert_eq!(parsed.pack_checksum, pack_checksum);
1655 assert_eq!(parsed.entries.len(), 1);
1656 assert_eq!(
1657 parsed
1658 .find(&oid)
1659 .expect("test operation should succeed")
1660 .offset,
1661 0x1234_5678
1662 );
1663 assert_eq!(
1664 parsed
1665 .find(&oid)
1666 .expect("test operation should succeed")
1667 .crc32,
1668 0
1669 );
1670 assert_pack_index_view_matches_owned(&index, ObjectFormat::Sha1);
1671 }
1672
1673 #[test]
1674 fn rejects_bad_pack_index_v1_checksum() {
1675 let oid = ObjectId::from_hex(
1676 ObjectFormat::Sha1,
1677 "ce013625030ba8dba906f756967f9e9ca394464a",
1678 )
1679 .expect("test operation should succeed");
1680 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1681 .expect("test operation should succeed");
1682 let mut index = single_entry_index_v1(ObjectFormat::Sha1, oid, 12, pack_checksum);
1683 let last = index.len() - 1;
1684 index[last] ^= 1;
1685 assert!(PackIndex::parse(&index, ObjectFormat::Sha1).is_err());
1686 }
1687
1688 #[test]
1689 fn pack_index_view_reads_v2_large_offsets() {
1690 let first = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"large offset a\n")
1691 .expect("test operation should succeed");
1692 let second =
1693 sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"large offset b\n")
1694 .expect("test operation should succeed");
1695 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1696 .expect("test operation should succeed");
1697 let entries = vec![
1698 PackIndexEntry {
1699 oid: first,
1700 crc32: 0x1111_2222,
1701 offset: 0x8000_0000,
1702 },
1703 PackIndexEntry {
1704 oid: second,
1705 crc32: 0x3333_4444,
1706 offset: 0x1_0000_0042,
1707 },
1708 ];
1709 let index = PackIndex::write_v2(ObjectFormat::Sha1, &entries, &pack_checksum)
1710 .expect("test operation should succeed");
1711
1712 assert_pack_index_view_matches_owned(&index, ObjectFormat::Sha1);
1713 let view = PackIndexView::parse(&index, ObjectFormat::Sha1)
1714 .expect("test operation should succeed");
1715 for entry in entries {
1716 assert_eq!(
1717 view.find(&entry.oid),
1718 Some(PackIndexLookup {
1719 crc32: entry.crc32,
1720 offset: entry.offset,
1721 })
1722 );
1723 }
1724 }
1725
1726 #[test]
1727 fn pack_index_view_default_parse_checks_index_checksum() {
1728 let oid = ObjectId::from_hex(
1729 ObjectFormat::Sha1,
1730 "ce013625030ba8dba906f756967f9e9ca394464a",
1731 )
1732 .expect("test operation should succeed");
1733 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1734 .expect("test operation should succeed");
1735 let mut index = single_entry_index(ObjectFormat::Sha1, oid, 0x1234_5678, 12, pack_checksum);
1736 let last = index.len() - 1;
1737 index[last] ^= 1;
1738
1739 assert!(PackIndexView::parse(&index, ObjectFormat::Sha1).is_err());
1740 let view = PackIndexView::parse_without_checksum(&index, ObjectFormat::Sha1)
1741 .expect("test operation should succeed");
1742 let trusted_view = PackIndexViewData::parse_trusted_without_checksum(
1743 Arc::from(index.clone().into_boxed_slice()),
1744 ObjectFormat::Sha1,
1745 )
1746 .expect("test operation should succeed");
1747 assert_eq!(
1748 view.find(&oid),
1749 Some(PackIndexLookup {
1750 crc32: 0x1234_5678,
1751 offset: 12,
1752 })
1753 );
1754 assert_eq!(
1755 trusted_view.find(&oid),
1756 Some(PackIndexLookup {
1757 crc32: 0x1234_5678,
1758 offset: 12,
1759 })
1760 );
1761 }
1762
1763 #[test]
1764 fn reverse_index_resolves_oid_at_offset() {
1765 let objects = (0..3)
1766 .map(|idx| {
1767 EncodedObject::new(
1768 ObjectType::Blob,
1769 format!("reverse index lookup object {idx}\n").into_bytes(),
1770 )
1771 })
1772 .collect::<Vec<_>>();
1773 let written = PackFile::write_packed(&objects, ObjectFormat::Sha1)
1774 .expect("test operation should succeed");
1775 let index = PackIndex::parse(&written.index, ObjectFormat::Sha1)
1776 .expect("test operation should succeed");
1777 let view = PackIndexViewData::parse_trusted_without_checksum(
1778 Arc::from(written.index.into_boxed_slice()),
1779 ObjectFormat::Sha1,
1780 )
1781 .expect("test operation should succeed");
1782 let positions = pack_order_index_positions(&index.entries);
1783 let reverse = PackReverseIndex::parse(
1784 &PackReverseIndex::write(ObjectFormat::Sha1, &positions, &index.pack_checksum)
1785 .expect("test operation should succeed"),
1786 ObjectFormat::Sha1,
1787 index.entries.len(),
1788 )
1789 .expect("test operation should succeed");
1790
1791 for entry in &index.entries {
1792 assert_eq!(
1793 reverse
1794 .oid_at_offset(&view, entry.offset)
1795 .expect("test operation should succeed"),
1796 entry.oid
1797 );
1798 }
1799 assert!(reverse.oid_at_offset(&view, 999).is_none());
1800 }
1801
1802 #[test]
1803 fn parses_pack_reverse_index() {
1804 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1805 .expect("test operation should succeed");
1806 let reverse_index = PackReverseIndex::write(ObjectFormat::Sha1, &[2, 0, 1], &pack_checksum)
1807 .expect("test operation should succeed");
1808 let parsed = PackReverseIndex::parse(&reverse_index, ObjectFormat::Sha1, 3)
1809 .expect("test operation should succeed");
1810 assert_eq!(parsed.version, 1);
1811 assert_eq!(parsed.format, ObjectFormat::Sha1);
1812 assert_eq!(parsed.positions, vec![2, 0, 1]);
1813 assert_eq!(parsed.pack_checksum, pack_checksum);
1814 assert_eq!(
1815 PackReverseIndex::write(ObjectFormat::Sha1, &parsed.positions, &parsed.pack_checksum)
1816 .expect("test operation should succeed"),
1817 reverse_index
1818 );
1819 }
1820
1821 #[test]
1822 fn rejects_bad_pack_reverse_index_checksum() {
1823 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1824 .expect("test operation should succeed");
1825 let mut reverse_index = PackReverseIndex::write(ObjectFormat::Sha1, &[0], &pack_checksum)
1826 .expect("test operation should succeed");
1827 let last = reverse_index.len() - 1;
1828 reverse_index[last] ^= 1;
1829 assert!(matches!(
1830 PackReverseIndex::parse(&reverse_index, ObjectFormat::Sha1, 1),
1831 Err(GitError::InvalidFormat(message)) if message == "invalid checksum"
1832 ));
1833 }
1834
1835 #[test]
1836 fn classifies_pack_reverse_index_corruption_for_fsck() {
1837 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1838 .expect("test operation should succeed");
1839 let valid = PackReverseIndex::write(ObjectFormat::Sha1, &[0], &pack_checksum)
1840 .expect("test operation should succeed");
1841 for (offset, value, expected) in [
1842 (1, 7, "unknown signature"),
1843 (7, 2, "unsupported version 2"),
1844 (11, 3, "unsupported hash id 3"),
1845 (14, 7, "invalid rev-index position 1792"),
1846 ] {
1847 let mut corrupt = valid.clone();
1848 corrupt[offset] = value;
1849 assert!(matches!(
1850 PackReverseIndex::parse(&corrupt, ObjectFormat::Sha1, 1),
1851 Err(GitError::InvalidFormat(message)) if message == expected
1852 ));
1853 }
1854 }
1855
1856 #[test]
1857 fn rejects_bad_pack_reverse_index_positions() {
1858 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1859 .expect("test operation should succeed");
1860 let duplicate = pack_reverse_index(ObjectFormat::Sha1, &[0, 0], pack_checksum.clone());
1861 assert!(PackReverseIndex::parse(&duplicate, ObjectFormat::Sha1, 2).is_err());
1862 let out_of_range = pack_reverse_index(ObjectFormat::Sha1, &[0, 2], pack_checksum);
1863 assert!(PackReverseIndex::parse(&out_of_range, ObjectFormat::Sha1, 2).is_err());
1864 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1865 .expect("test operation should succeed");
1866 assert!(PackReverseIndex::write(ObjectFormat::Sha1, &[0, 0], &pack_checksum).is_err());
1867 assert!(PackReverseIndex::write(ObjectFormat::Sha1, &[0, 2], &pack_checksum).is_err());
1868 }
1869
1870 #[test]
1871 fn parses_pack_mtimes() {
1872 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1873 .expect("test operation should succeed");
1874 let mtimes = PackMtimes::write(
1875 ObjectFormat::Sha1,
1876 &[1, 1_700_000_000, u32::MAX],
1877 &pack_checksum,
1878 )
1879 .expect("test operation should succeed");
1880 let parsed = PackMtimes::parse(&mtimes, ObjectFormat::Sha1, 3)
1881 .expect("test operation should succeed");
1882 assert_eq!(parsed.version, 1);
1883 assert_eq!(parsed.format, ObjectFormat::Sha1);
1884 assert_eq!(parsed.mtimes, vec![1, 1_700_000_000, u32::MAX]);
1885 assert_eq!(parsed.pack_checksum, pack_checksum);
1886 assert_eq!(
1887 PackMtimes::write(ObjectFormat::Sha1, &parsed.mtimes, &parsed.pack_checksum)
1888 .expect("test operation should succeed"),
1889 mtimes
1890 );
1891 }
1892
1893 #[test]
1894 fn rejects_bad_pack_mtimes_checksum() {
1895 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1896 .expect("test operation should succeed");
1897 let mut mtimes = PackMtimes::write(ObjectFormat::Sha1, &[1], &pack_checksum)
1898 .expect("test operation should succeed");
1899 let last = mtimes.len() - 1;
1900 mtimes[last] ^= 1;
1901 assert!(PackMtimes::parse(&mtimes, ObjectFormat::Sha1, 1).is_err());
1902 }
1903
1904 #[test]
1905 fn rejects_bad_pack_mtimes_shape() {
1906 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1907 .expect("test operation should succeed");
1908 let mtimes = pack_mtimes(ObjectFormat::Sha1, &[1, 2], pack_checksum.clone());
1909 assert!(PackMtimes::parse(&mtimes, ObjectFormat::Sha1, 1).is_err());
1910
1911 let mut wrong_hash = pack_mtimes(ObjectFormat::Sha1, &[1], pack_checksum);
1912 wrong_hash[11] = 2;
1913 let checksum_offset = wrong_hash.len() - ObjectFormat::Sha1.raw_len();
1914 let checksum = sley_core::digest_bytes(ObjectFormat::Sha1, &wrong_hash[..checksum_offset])
1915 .expect("test operation should succeed");
1916 wrong_hash[checksum_offset..].copy_from_slice(checksum.as_bytes());
1917 assert!(PackMtimes::parse(&wrong_hash, ObjectFormat::Sha1, 1).is_err());
1918 }
1919
1920 #[test]
1921 fn parses_multi_pack_index_header_and_chunk_lookup() {
1922 let first = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"first object\n")
1923 .expect("test operation should succeed");
1924 let second = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"second object\n")
1925 .expect("test operation should succeed");
1926 let chunks = midx_chunks_with_pack_names(
1927 ObjectFormat::Sha1,
1928 b"pack-a.idx\0pack-b.idx\0\0\0".to_vec(),
1929 &[(first.clone(), 0, 12), (second.clone(), 1, 0x1_0000_0000)],
1930 );
1931 let midx = multi_pack_index(ObjectFormat::Sha1, 2, 2, &chunks);
1932 let parsed = MultiPackIndex::parse(&midx, ObjectFormat::Sha1)
1933 .expect("test operation should succeed");
1934 assert_eq!(parsed.version, 2);
1935 assert_eq!(parsed.format, ObjectFormat::Sha1);
1936 assert_eq!(parsed.pack_count, 2);
1937 assert_eq!(parsed.pack_names, vec!["pack-a.idx", "pack-b.idx"]);
1938 assert_eq!(parsed.object_count, 2);
1939 assert_eq!(parsed.objects.len(), 2);
1940 assert_eq!(
1941 parsed
1942 .find(&first)
1943 .expect("test operation should succeed")
1944 .pack_int_id,
1945 0
1946 );
1947 assert_eq!(
1948 parsed
1949 .find(&first)
1950 .expect("test operation should succeed")
1951 .offset,
1952 12
1953 );
1954 assert_eq!(
1955 parsed
1956 .find(&second)
1957 .expect("test operation should succeed")
1958 .pack_int_id,
1959 1
1960 );
1961 assert_eq!(
1962 parsed
1963 .find(&second)
1964 .expect("test operation should succeed")
1965 .offset,
1966 0x1_0000_0000
1967 );
1968 assert_eq!(parsed.reverse_index, None);
1969 assert_eq!(parsed.bitmapped_packs, None);
1970 assert_eq!(parsed.chunks.len(), 5);
1971 assert_eq!(parsed.chunks[0].id, *b"PNAM");
1972 assert_eq!(parsed.chunks[0].offset, 84);
1973 assert_eq!(parsed.chunks[0].len, 24);
1974 assert_eq!(parsed.chunks[1].id, *b"OIDF");
1975 assert_eq!(parsed.chunks[1].offset, 108);
1976 assert_eq!(parsed.chunks[1].len, 1024);
1977 }
1978
1979 #[test]
1980 fn raw_multi_pack_index_lookup_finds_pack_and_offset() {
1981 let first = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"first object\n")
1982 .expect("test operation should succeed");
1983 let second = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"second object\n")
1984 .expect("test operation should succeed");
1985 let missing = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"missing\n")
1986 .expect("test operation should succeed");
1987 let chunks = midx_chunks_with_pack_names(
1988 ObjectFormat::Sha1,
1989 b"pack-a.idx\0pack-b.idx\0\0\0".to_vec(),
1990 &[(first.clone(), 0, 12), (second.clone(), 1, 0x1_0000_0000)],
1991 );
1992 let midx = Arc::new(multi_pack_index(ObjectFormat::Sha1, 2, 2, &chunks));
1993 let lookup = MultiPackIndexOidLookup::parse(midx, ObjectFormat::Sha1)
1994 .expect("test operation should succeed");
1995
1996 assert!(lookup.contains(&first));
1997 assert!(lookup.contains(&second));
1998 assert!(!lookup.contains(&missing));
1999
2000 let first_entry = lookup
2001 .find(&first)
2002 .expect("test operation should succeed")
2003 .expect("object should be present");
2004 assert_eq!(
2005 lookup.pack_name(first_entry.pack_int_id),
2006 Some("pack-a.idx")
2007 );
2008 assert_eq!(first_entry.offset, 12);
2009
2010 let second_entry = lookup
2011 .find(&second)
2012 .expect("test operation should succeed")
2013 .expect("object should be present");
2014 assert_eq!(
2015 lookup.pack_name(second_entry.pack_int_id),
2016 Some("pack-b.idx")
2017 );
2018 assert_eq!(second_entry.offset, 0x1_0000_0000);
2019 assert!(
2020 lookup
2021 .find(&missing)
2022 .expect("test operation should succeed")
2023 .is_none()
2024 );
2025 }
2026
2027 #[test]
2028 fn rejects_bad_multi_pack_index_checksum() {
2029 let chunks = midx_chunks_with_pack_names(ObjectFormat::Sha1, Vec::new(), &[]);
2030 let mut midx = multi_pack_index(ObjectFormat::Sha1, 1, 0, &chunks);
2031 let last = midx.len() - 1;
2032 midx[last] ^= 1;
2033 assert!(MultiPackIndex::parse(&midx, ObjectFormat::Sha1).is_err());
2034 }
2035
2036 #[test]
2037 fn rejects_bad_multi_pack_index_shape() {
2038 let chunks = midx_chunks_with_pack_names(ObjectFormat::Sha1, Vec::new(), &[]);
2039 let mut wrong_hash = multi_pack_index(ObjectFormat::Sha1, 1, 0, &chunks);
2040 wrong_hash[5] = 2;
2041 let checksum_offset = wrong_hash.len() - ObjectFormat::Sha1.raw_len();
2042 let checksum = sley_core::digest_bytes(ObjectFormat::Sha1, &wrong_hash[..checksum_offset])
2043 .expect("test operation should succeed");
2044 wrong_hash[checksum_offset..].copy_from_slice(checksum.as_bytes());
2045 assert!(MultiPackIndex::parse(&wrong_hash, ObjectFormat::Sha1).is_err());
2046
2047 let mut missing_terminator = multi_pack_index(ObjectFormat::Sha1, 1, 0, &chunks);
2048 missing_terminator[12] = b'B';
2049 let checksum_offset = missing_terminator.len() - ObjectFormat::Sha1.raw_len();
2050 let checksum =
2051 sley_core::digest_bytes(ObjectFormat::Sha1, &missing_terminator[..checksum_offset])
2052 .expect("test operation should succeed");
2053 missing_terminator[checksum_offset..].copy_from_slice(checksum.as_bytes());
2054 assert!(MultiPackIndex::parse(&missing_terminator, ObjectFormat::Sha1).is_err());
2055
2056 let mut bad_offset = multi_pack_index(
2057 ObjectFormat::Sha1,
2058 2,
2059 0,
2060 &midx_chunks_with_pack_names(ObjectFormat::Sha1, Vec::new(), &[]),
2061 );
2062 bad_offset[16..24].copy_from_slice(&0u64.to_be_bytes());
2063 let checksum_offset = bad_offset.len() - ObjectFormat::Sha1.raw_len();
2064 let checksum = sley_core::digest_bytes(ObjectFormat::Sha1, &bad_offset[..checksum_offset])
2065 .expect("test operation should succeed");
2066 bad_offset[checksum_offset..].copy_from_slice(checksum.as_bytes());
2067 assert!(MultiPackIndex::parse(&bad_offset, ObjectFormat::Sha1).is_err());
2068 }
2069
2070 #[test]
2071 fn rejects_bad_multi_pack_index_pack_names() {
2072 let missing = multi_pack_index(ObjectFormat::Sha1, 2, 1, &[]);
2073 assert!(MultiPackIndex::parse(&missing, ObjectFormat::Sha1).is_err());
2074
2075 let too_few = multi_pack_index(
2076 ObjectFormat::Sha1,
2077 2,
2078 2,
2079 &midx_chunks_with_pack_names(ObjectFormat::Sha1, b"pack-a.idx\0".to_vec(), &[]),
2080 );
2081 assert!(MultiPackIndex::parse(&too_few, ObjectFormat::Sha1).is_err());
2082
2083 let bad_padding = multi_pack_index(
2084 ObjectFormat::Sha1,
2085 2,
2086 1,
2087 &midx_chunks_with_pack_names(ObjectFormat::Sha1, b"pack-a.idx\0xxxx".to_vec(), &[]),
2088 );
2089 assert!(MultiPackIndex::parse(&bad_padding, ObjectFormat::Sha1).is_err());
2090
2091 let unsorted_v1 = multi_pack_index(
2092 ObjectFormat::Sha1,
2093 1,
2094 2,
2095 &midx_chunks_with_pack_names(
2096 ObjectFormat::Sha1,
2097 b"pack-b.idx\0pack-a.idx\0".to_vec(),
2098 &[],
2099 ),
2100 );
2101 assert!(MultiPackIndex::parse(&unsorted_v1, ObjectFormat::Sha1).is_err());
2102
2103 let unsorted_v2 = multi_pack_index(
2104 ObjectFormat::Sha1,
2105 2,
2106 2,
2107 &midx_chunks_with_pack_names(
2108 ObjectFormat::Sha1,
2109 b"pack-b.idx\0pack-a.idx\0".to_vec(),
2110 &[],
2111 ),
2112 );
2113 let parsed = MultiPackIndex::parse(&unsorted_v2, ObjectFormat::Sha1)
2114 .expect("test operation should succeed");
2115 assert_eq!(parsed.pack_names, vec!["pack-b.idx", "pack-a.idx"]);
2116 }
2117
2118 #[test]
2119 fn rejects_bad_multi_pack_index_object_tables() {
2120 let oid_a = ObjectId::from_hex(
2121 ObjectFormat::Sha1,
2122 "1111111111111111111111111111111111111111",
2123 )
2124 .expect("test operation should succeed");
2125 let oid_b = ObjectId::from_hex(
2126 ObjectFormat::Sha1,
2127 "2222222222222222222222222222222222222222",
2128 )
2129 .expect("test operation should succeed");
2130
2131 let missing_oidf = multi_pack_index(
2132 ObjectFormat::Sha1,
2133 2,
2134 1,
2135 &[(*b"PNAM", b"pack-a.idx\0\0".to_vec())],
2136 );
2137 assert!(MultiPackIndex::parse(&missing_oidf, ObjectFormat::Sha1).is_err());
2138
2139 let bad_fanout = vec![
2140 (*b"PNAM", b"pack-a.idx\0\0".to_vec()),
2141 (*b"OIDF", vec![0; 256 * 4]),
2142 (*b"OIDL", oid_a.as_bytes().to_vec()),
2143 (*b"OOFF", midx_ooff_entries(&[(0, 12)], &mut Vec::new())),
2144 ];
2145 let bad_fanout = multi_pack_index(ObjectFormat::Sha1, 2, 1, &bad_fanout);
2146 assert!(MultiPackIndex::parse(&bad_fanout, ObjectFormat::Sha1).is_err());
2147
2148 let mut unsorted = Vec::new();
2149 unsorted.push((*b"PNAM", b"pack-a.idx\0\0".to_vec()));
2150 unsorted.push((*b"OIDF", midx_oid_fanout(&[oid_a.clone(), oid_b.clone()])));
2151 let mut oid_lookup = Vec::new();
2152 oid_lookup.extend_from_slice(oid_b.as_bytes());
2153 oid_lookup.extend_from_slice(oid_a.as_bytes());
2154 unsorted.push((*b"OIDL", oid_lookup));
2155 unsorted.push((
2156 *b"OOFF",
2157 midx_ooff_entries(&[(0, 12), (0, 24)], &mut Vec::new()),
2158 ));
2159 let unsorted = multi_pack_index(ObjectFormat::Sha1, 2, 1, &unsorted);
2160 assert!(MultiPackIndex::parse(&unsorted, ObjectFormat::Sha1).is_err());
2161
2162 let bad_pack = multi_pack_index(
2163 ObjectFormat::Sha1,
2164 2,
2165 1,
2166 &midx_chunks_with_pack_names(
2167 ObjectFormat::Sha1,
2168 b"pack-a.idx\0\0".to_vec(),
2169 &[(oid_a.clone(), 1, 12)],
2170 ),
2171 );
2172 assert!(MultiPackIndex::parse(&bad_pack, ObjectFormat::Sha1).is_err());
2173
2174 let mut large_offsets = Vec::new();
2175 let missing_loff = vec![
2176 (*b"PNAM", b"pack-a.idx\0\0".to_vec()),
2177 (*b"OIDF", midx_oid_fanout(std::slice::from_ref(&oid_a))),
2178 (*b"OIDL", oid_a.as_bytes().to_vec()),
2179 (
2180 *b"OOFF",
2181 midx_ooff_entries(&[(0, 0x1_0000_0000)], &mut large_offsets),
2182 ),
2183 ];
2184 let missing_loff = multi_pack_index(ObjectFormat::Sha1, 2, 1, &missing_loff);
2185 assert!(MultiPackIndex::parse(&missing_loff, ObjectFormat::Sha1).is_err());
2186
2187 let mut bad_loff =
2188 midx_chunks_with_pack_names(ObjectFormat::Sha1, b"pack-a.idx\0\0".to_vec(), &[]);
2189 bad_loff.push((*b"LOFF", vec![0]));
2190 let bad_loff = multi_pack_index(ObjectFormat::Sha1, 2, 1, &bad_loff);
2191 assert!(MultiPackIndex::parse(&bad_loff, ObjectFormat::Sha1).is_err());
2192 }
2193
2194 #[test]
2195 fn parses_multi_pack_index_bitmap_chunks() {
2196 let first = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"first object\n")
2197 .expect("test operation should succeed");
2198 let second = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"second object\n")
2199 .expect("test operation should succeed");
2200 let mut chunks = midx_chunks_with_pack_names(
2201 ObjectFormat::Sha1,
2202 b"pack-a.idx\0pack-b.idx\0\0\0".to_vec(),
2203 &[(first, 0, 12), (second, 1, 24)],
2204 );
2205 chunks.push((*b"RIDX", midx_u32_table(&[1, 0])));
2206 chunks.push((*b"BTMP", midx_bitmap_packs(&[(0, 1), (1, 1)])));
2207 let midx = multi_pack_index(ObjectFormat::Sha1, 2, 2, &chunks);
2208
2209 let parsed = MultiPackIndex::parse(&midx, ObjectFormat::Sha1)
2210 .expect("test operation should succeed");
2211 assert_eq!(parsed.reverse_index, Some(vec![1, 0]));
2212 assert_eq!(
2213 parsed.bitmapped_packs,
2214 Some(vec![
2215 MultiPackBitmapPack {
2216 bitmap_pos: 0,
2217 bitmap_nr: 1,
2218 },
2219 MultiPackBitmapPack {
2220 bitmap_pos: 1,
2221 bitmap_nr: 1,
2222 },
2223 ])
2224 );
2225 }
2226
2227 #[test]
2228 fn writes_multi_pack_index_that_round_trips() {
2229 let first = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"first object\n")
2230 .expect("test operation should succeed");
2231 let second = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"second object\n")
2232 .expect("test operation should succeed");
2233 let bytes = MultiPackIndex::write(
2234 ObjectFormat::Sha1,
2235 2,
2236 &["pack-b.idx".into(), "pack-a.idx".into()],
2237 &[
2238 MultiPackIndexEntry {
2239 oid: second.clone(),
2240 pack_int_id: 0,
2241 offset: 0x1_0000_0000,
2242 force_large_offset: false,
2243 },
2244 MultiPackIndexEntry {
2245 oid: first.clone(),
2246 pack_int_id: 1,
2247 offset: 12,
2248 force_large_offset: false,
2249 },
2250 ],
2251 )
2252 .expect("test operation should succeed");
2253
2254 let parsed = MultiPackIndex::parse(&bytes, ObjectFormat::Sha1)
2255 .expect("test operation should succeed");
2256 assert_eq!(parsed.version, 2);
2257 assert_eq!(parsed.pack_names, vec!["pack-b.idx", "pack-a.idx"]);
2258 assert_eq!(parsed.object_count, 2);
2259 assert_eq!(
2260 parsed
2261 .find(&first)
2262 .expect("test operation should succeed")
2263 .pack_int_id,
2264 1
2265 );
2266 assert_eq!(
2267 parsed
2268 .find(&first)
2269 .expect("test operation should succeed")
2270 .offset,
2271 12
2272 );
2273 assert_eq!(
2274 parsed
2275 .find(&second)
2276 .expect("test operation should succeed")
2277 .pack_int_id,
2278 0
2279 );
2280 assert_eq!(
2281 parsed
2282 .find(&second)
2283 .expect("test operation should succeed")
2284 .offset,
2285 0x1_0000_0000
2286 );
2287 assert!(parsed.chunks.iter().any(|chunk| chunk.id == *b"LOFF"));
2288 }
2289
2290 #[test]
2291 fn write_multi_pack_index_rejects_invalid_inputs() {
2292 let oid = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"object\n")
2293 .expect("test operation should succeed");
2294 assert!(MultiPackIndex::write(ObjectFormat::Sha1, 3, &["pack-a.idx".into()], &[]).is_err());
2295 assert!(
2296 MultiPackIndex::write(
2297 ObjectFormat::Sha1,
2298 1,
2299 &["pack-b.idx".into(), "pack-a.idx".into()],
2300 &[],
2301 )
2302 .is_err()
2303 );
2304 assert!(MultiPackIndex::write(ObjectFormat::Sha1, 2, &["pack/a.idx".into()], &[]).is_err());
2305 assert!(
2306 MultiPackIndex::write(
2307 ObjectFormat::Sha1,
2308 2,
2309 &["pack-a.idx".into()],
2310 &[MultiPackIndexEntry {
2311 oid,
2312 pack_int_id: 1,
2313 offset: 12,
2314 force_large_offset: false,
2315 }],
2316 )
2317 .is_err()
2318 );
2319 assert!(
2320 MultiPackIndex::write(
2321 ObjectFormat::Sha1,
2322 2,
2323 &["pack-a.idx".into()],
2324 &[
2325 MultiPackIndexEntry {
2326 oid,
2327 pack_int_id: 0,
2328 offset: 12,
2329 force_large_offset: false,
2330 },
2331 MultiPackIndexEntry {
2332 oid,
2333 pack_int_id: 0,
2334 offset: 24,
2335 force_large_offset: false,
2336 },
2337 ],
2338 )
2339 .is_err()
2340 );
2341 }
2342
2343 #[test]
2344 fn rejects_bad_multi_pack_index_bitmap_chunks() {
2345 let oid_a = ObjectId::from_hex(
2346 ObjectFormat::Sha1,
2347 "1111111111111111111111111111111111111111",
2348 )
2349 .expect("test operation should succeed");
2350 let oid_b = ObjectId::from_hex(
2351 ObjectFormat::Sha1,
2352 "2222222222222222222222222222222222222222",
2353 )
2354 .expect("test operation should succeed");
2355
2356 let mut duplicate_ridx = midx_chunks_with_pack_names(
2357 ObjectFormat::Sha1,
2358 b"pack-a.idx\0\0".to_vec(),
2359 &[(oid_a.clone(), 0, 12), (oid_b.clone(), 0, 24)],
2360 );
2361 duplicate_ridx.push((*b"RIDX", midx_u32_table(&[0, 0])));
2362 let duplicate_ridx = multi_pack_index(ObjectFormat::Sha1, 2, 1, &duplicate_ridx);
2363 assert!(MultiPackIndex::parse(&duplicate_ridx, ObjectFormat::Sha1).is_err());
2364
2365 let mut short_btmp = midx_chunks_with_pack_names(
2366 ObjectFormat::Sha1,
2367 b"pack-a.idx\0pack-b.idx\0\0\0".to_vec(),
2368 &[(oid_a.clone(), 0, 12), (oid_b.clone(), 1, 24)],
2369 );
2370 short_btmp.push((*b"BTMP", midx_bitmap_packs(&[(0, 1)])));
2371 let short_btmp = multi_pack_index(ObjectFormat::Sha1, 2, 2, &short_btmp);
2372 assert!(MultiPackIndex::parse(&short_btmp, ObjectFormat::Sha1).is_err());
2373
2374 let mut out_of_range_btmp = midx_chunks_with_pack_names(
2375 ObjectFormat::Sha1,
2376 b"pack-a.idx\0\0".to_vec(),
2377 &[(oid_a, 0, 12), (oid_b, 0, 24)],
2378 );
2379 out_of_range_btmp.push((*b"BTMP", midx_bitmap_packs(&[(1, 2)])));
2380 let out_of_range_btmp = multi_pack_index(ObjectFormat::Sha1, 2, 1, &out_of_range_btmp);
2381 assert!(MultiPackIndex::parse(&out_of_range_btmp, ObjectFormat::Sha1).is_err());
2382 }
2383
2384 #[test]
2385 fn parses_pack_bitmap_index_with_hash_cache() {
2386 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
2387 .expect("test operation should succeed");
2388 let bitmap = pack_bitmap_index(
2389 ObjectFormat::Sha1,
2390 3,
2391 PackBitmapIndex::OPTION_FULL_DAG | PackBitmapIndex::OPTION_HASH_CACHE,
2392 &pack_checksum,
2393 &[(2, 0, 1, &[0b101])],
2394 Some(&[0x1111_1111, 0x2222_2222, 0x3333_3333]),
2395 );
2396
2397 let parsed = PackBitmapIndex::parse(&bitmap, ObjectFormat::Sha1, 3)
2398 .expect("test operation should succeed");
2399 assert_eq!(parsed.version, 1);
2400 assert_eq!(parsed.format, ObjectFormat::Sha1);
2401 assert_eq!(
2402 parsed.options,
2403 PackBitmapIndex::OPTION_FULL_DAG | PackBitmapIndex::OPTION_HASH_CACHE
2404 );
2405 assert_eq!(parsed.pack_checksum, pack_checksum);
2406 assert_eq!(parsed.type_bitmaps.commits.bit_size, 3);
2407 assert_eq!(parsed.type_bitmaps.trees.bit_size, 3);
2408 assert_eq!(parsed.entries.len(), 1);
2409 let entry = parsed
2410 .entry_for_index_position(2)
2411 .expect("test operation should succeed");
2412 assert_eq!(entry.xor_offset, 0);
2413 assert_eq!(entry.flags, 1);
2414 assert_eq!(entry.bitmap.words, ewah_literal_words(&[0b101]));
2415 assert_eq!(
2416 parsed.name_hash_cache,
2417 Some(vec![0x1111_1111, 0x2222_2222, 0x3333_3333])
2418 );
2419 }
2420
2421 #[test]
2422 fn parses_pack_bitmap_index_sha256() {
2423 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha256, b"pack")
2424 .expect("test operation should succeed");
2425 let bitmap = pack_bitmap_index(
2426 ObjectFormat::Sha256,
2427 2,
2428 PackBitmapIndex::OPTION_FULL_DAG,
2429 &pack_checksum,
2430 &[(0, 0, 0, &[0b11])],
2431 None,
2432 );
2433
2434 let parsed = PackBitmapIndex::parse(&bitmap, ObjectFormat::Sha256, 2)
2435 .expect("test operation should succeed");
2436 assert_eq!(parsed.version, 1);
2437 assert_eq!(parsed.format, ObjectFormat::Sha256);
2438 assert_eq!(parsed.pack_checksum, pack_checksum);
2439 assert_eq!(parsed.index_checksum.format(), ObjectFormat::Sha256);
2440 assert_eq!(parsed.entries[0].object_position, 0);
2441 assert_eq!(parsed.name_hash_cache, None);
2442 }
2443
2444 #[test]
2445 fn parses_upstream_git_written_pack_bitmap_index() {
2446 let root = unique_temp_dir("git-pack-bitmap-upstream");
2447 fs::create_dir_all(&root).expect("test operation should succeed");
2448 {
2449 run_git_success(&root, &["init", "-q", "-b", "main"]);
2450 run_git_success(
2451 &root,
2452 &[
2453 "-c",
2454 "user.name=Example User",
2455 "-c",
2456 "user.email=example@example.invalid",
2457 "commit",
2458 "--allow-empty",
2459 "-q",
2460 "-m",
2461 "one",
2462 ],
2463 );
2464 run_git_success(
2465 &root,
2466 &[
2467 "-c",
2468 "user.name=Example User",
2469 "-c",
2470 "user.email=example@example.invalid",
2471 "commit",
2472 "--allow-empty",
2473 "-q",
2474 "-m",
2475 "two",
2476 ],
2477 );
2478 run_git_success(&root, &["repack", "-adb"]);
2479 let pack_dir = root.join(".git").join("objects").join("pack");
2480 let idx_path = single_path_with_extension(&pack_dir, "idx");
2481 let bitmap_path = single_path_with_extension(&pack_dir, "bitmap");
2482 let index = PackIndex::parse(
2483 &fs::read(idx_path).expect("test operation should succeed"),
2484 ObjectFormat::Sha1,
2485 )
2486 .expect("test operation should succeed");
2487 let bitmap = PackBitmapIndex::parse(
2488 &fs::read(bitmap_path).expect("test operation should succeed"),
2489 ObjectFormat::Sha1,
2490 index.entries.len(),
2491 )
2492 .expect("test operation should succeed");
2493 assert_eq!(bitmap.pack_checksum, index.pack_checksum);
2494 assert!(!bitmap.entries.is_empty());
2495 };
2496 let _ = fs::remove_dir_all(&root);
2497 }
2498
2499 #[test]
2500 fn rejects_bad_pack_bitmap_index_header_and_checksum() {
2501 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
2502 .expect("test operation should succeed");
2503 let bitmap = pack_bitmap_index(
2504 ObjectFormat::Sha1,
2505 1,
2506 PackBitmapIndex::OPTION_FULL_DAG,
2507 &pack_checksum,
2508 &[(0, 0, 0, &[1])],
2509 None,
2510 );
2511
2512 let mut bad_signature = bitmap.clone();
2513 bad_signature[0] = b'X';
2514 assert!(PackBitmapIndex::parse(&bad_signature, ObjectFormat::Sha1, 1).is_err());
2515
2516 let mut bad_version = bitmap.clone();
2517 bad_version[5] = 2;
2518 refresh_trailing_checksum(ObjectFormat::Sha1, &mut bad_version);
2519 assert!(PackBitmapIndex::parse(&bad_version, ObjectFormat::Sha1, 1).is_err());
2520
2521 let mut bad_option = bitmap.clone();
2522 bad_option[7] = 0x20;
2523 refresh_trailing_checksum(ObjectFormat::Sha1, &mut bad_option);
2524 assert!(PackBitmapIndex::parse(&bad_option, ObjectFormat::Sha1, 1).is_err());
2525
2526 let mut bad_checksum = bitmap;
2527 let last = bad_checksum.len() - 1;
2528 bad_checksum[last] ^= 1;
2529 assert!(PackBitmapIndex::parse(&bad_checksum, ObjectFormat::Sha1, 1).is_err());
2530 }
2531
2532 #[test]
2533 fn rejects_bad_pack_bitmap_index_ewah_and_entries() {
2534 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
2535 .expect("test operation should succeed");
2536 let bitmap = pack_bitmap_index(
2537 ObjectFormat::Sha1,
2538 2,
2539 PackBitmapIndex::OPTION_FULL_DAG,
2540 &pack_checksum,
2541 &[(0, 0, 0, &[0b01]), (1, 1, 0, &[0b11])],
2542 None,
2543 );
2544
2545 let mut truncated = bitmap;
2546 truncated.truncate(truncated.len() - ObjectFormat::Sha1.raw_len() - 1);
2547 refresh_trailing_checksum(ObjectFormat::Sha1, &mut truncated);
2548 assert!(PackBitmapIndex::parse(&truncated, ObjectFormat::Sha1, 2).is_err());
2549
2550 let mut out_of_range_position = pack_bitmap_index(
2551 ObjectFormat::Sha1,
2552 2,
2553 PackBitmapIndex::OPTION_FULL_DAG,
2554 &pack_checksum,
2555 &[(2, 0, 0, &[0b01])],
2556 None,
2557 );
2558 assert!(PackBitmapIndex::parse(&out_of_range_position, ObjectFormat::Sha1, 2).is_err());
2559 refresh_trailing_checksum(ObjectFormat::Sha1, &mut out_of_range_position);
2560 assert!(PackBitmapIndex::parse(&out_of_range_position, ObjectFormat::Sha1, 2).is_err());
2561
2562 let invalid_xor = pack_bitmap_index(
2563 ObjectFormat::Sha1,
2564 2,
2565 PackBitmapIndex::OPTION_FULL_DAG,
2566 &pack_checksum,
2567 &[(0, 1, 0, &[0b01])],
2568 None,
2569 );
2570 assert!(PackBitmapIndex::parse(&invalid_xor, ObjectFormat::Sha1, 2).is_err());
2571 }
2572
2573 #[test]
2574 fn parses_single_entry_pack_index_sha256() {
2575 let oid = sley_core::object_id_for_bytes(ObjectFormat::Sha256, "blob", b"hello sha256\n")
2576 .expect("test operation should succeed");
2577 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha256, b"pack")
2578 .expect("test operation should succeed");
2579 let index = single_entry_index(
2580 ObjectFormat::Sha256,
2581 oid,
2582 0x1234_5678,
2583 12,
2584 pack_checksum.clone(),
2585 );
2586 let parsed =
2587 PackIndex::parse(&index, ObjectFormat::Sha256).expect("test operation should succeed");
2588 assert_eq!(parsed.version, 2);
2589 assert_eq!(parsed.pack_checksum, pack_checksum);
2590 assert_eq!(parsed.entries.len(), 1);
2591 assert_eq!(
2592 parsed
2593 .find(&oid)
2594 .expect("test operation should succeed")
2595 .offset,
2596 12
2597 );
2598 assert_eq!(
2599 parsed
2600 .find(&oid)
2601 .expect("test operation should succeed")
2602 .crc32,
2603 0x1234_5678
2604 );
2605 assert_eq!(parsed.index_checksum.format(), ObjectFormat::Sha256);
2606 assert_pack_index_view_matches_owned(&index, ObjectFormat::Sha256);
2607 }
2608
2609 #[test]
2610 fn write_packed_deltifies_similar_blobs_and_round_trips_sha1() {
2611 write_packed_deltifies_similar_blobs_and_round_trips(ObjectFormat::Sha1);
2612 }
2613
2614 #[test]
2615 fn write_packed_deltifies_similar_blobs_and_round_trips_sha256() {
2616 write_packed_deltifies_similar_blobs_and_round_trips(ObjectFormat::Sha256);
2617 }
2618
2619 #[test]
2620 fn write_packed_rejects_duplicate_objects() {
2621 let object = EncodedObject::new(ObjectType::Blob, b"same\n".to_vec());
2622 assert!(PackFile::write_packed(&[object.clone(), object], ObjectFormat::Sha1,).is_err());
2623 }
2624
2625 #[test]
2626 fn write_packed_with_known_ids_validates_ids_before_trusting_them() {
2627 let object = EncodedObject::new(ObjectType::Blob, b"same\n".to_vec());
2628 let sha1 = object
2629 .object_id(ObjectFormat::Sha1)
2630 .expect("test operation should succeed");
2631 let sha256 = object
2632 .object_id(ObjectFormat::Sha256)
2633 .expect("test operation should succeed");
2634 let duplicate = [
2635 PackInput {
2636 oid: &sha1,
2637 object: &object,
2638 },
2639 PackInput {
2640 oid: &sha1,
2641 object: &object,
2642 },
2643 ];
2644 assert!(PackFile::write_packed_with_known_ids(&duplicate, ObjectFormat::Sha1).is_err());
2645
2646 let wrong_format = [PackInput {
2647 oid: &sha256,
2648 object: &object,
2649 }];
2650 assert!(PackFile::write_packed_with_known_ids(&wrong_format, ObjectFormat::Sha1).is_err());
2651 }
2652
2653 #[test]
2654 fn write_packed_with_known_ids_to_writer_matches_in_memory_pack() {
2655 let objects = similar_blob_family(6);
2656 let object_ids = objects
2657 .iter()
2658 .map(|object| {
2659 object
2660 .object_id(ObjectFormat::Sha1)
2661 .expect("test operation should succeed")
2662 })
2663 .collect::<Vec<_>>();
2664 let inputs = objects
2665 .iter()
2666 .zip(&object_ids)
2667 .map(|(object, oid)| PackInput { oid, object })
2668 .collect::<Vec<_>>();
2669 let options = PackWriteOptions::new();
2670 let in_memory = PackFile::write_packed_with_known_ids_and_options(
2671 &inputs,
2672 ObjectFormat::Sha1,
2673 &options,
2674 )
2675 .expect("test operation should succeed");
2676 let mut written = Vec::new();
2677 let streamed = PackFile::write_packed_with_known_ids_to_writer(
2678 &inputs,
2679 ObjectFormat::Sha1,
2680 &options,
2681 &mut written,
2682 )
2683 .expect("test operation should succeed");
2684
2685 assert_eq!(written, in_memory.pack);
2686 assert_eq!(streamed.index, in_memory.index);
2687 assert_eq!(streamed.checksum, in_memory.checksum);
2688 assert_eq!(streamed.entries, in_memory.entries);
2689 assert_eq!(streamed.delta_count, in_memory.delta_count);
2690 assert_eq!(streamed.pack_size, in_memory.pack.len() as u64);
2691 }
2692
2693 #[test]
2694 fn write_packed_from_source_to_writer_deltifies_across_windows() {
2695 let format = ObjectFormat::Sha1;
2696 let shared = b"cross-window base payload with enough shared anchors\n".repeat(64);
2700 let mut base_body = shared.clone();
2701 base_body.extend_from_slice(b"base\n");
2702 let mut target_body = shared;
2703 target_body.extend_from_slice(b"target\n");
2704 let objects = vec![
2705 EncodedObject::new(ObjectType::Blob, base_body),
2706 EncodedObject::new(ObjectType::Blob, target_body),
2707 ];
2708
2709 let object_ids = objects
2710 .iter()
2711 .map(|object| {
2712 object
2713 .object_id(format)
2714 .expect("test operation should succeed")
2715 })
2716 .collect::<Vec<_>>();
2717 let base_oid = object_ids[0];
2718 let target_oid = object_ids[1];
2719 let object_map = object_ids
2720 .iter()
2721 .copied()
2722 .zip(objects.into_iter().map(Arc::new))
2723 .collect::<HashMap<_, _>>();
2724
2725 let options = PackWriteOptions::new().with_reorder(false).with_window(10);
2726 let one_object = object_map.get(&base_oid).expect("base").body.len() as u64 + 80;
2727 let limits =
2728 PackWriteLimits::new().with_compression_working_set(ByteBudget::new(one_object));
2729 let mut written = Vec::new();
2730 let summary = PackFile::write_packed_from_source_to_writer(
2731 object_ids.iter().copied(),
2732 2,
2733 format,
2734 &options,
2735 limits,
2736 |oid| {
2737 object_map
2738 .get(oid)
2739 .cloned()
2740 .ok_or_else(|| GitError::not_found(format!("missing test object {oid}")))
2741 },
2742 &mut written,
2743 )
2744 .expect("test operation should succeed");
2745
2746 assert!(
2747 summary.delta_count > 0,
2748 "expected source-backed streaming writer to find deltas"
2749 );
2750 let stats =
2751 PackFile::verify_pack_stats(&written, format).expect("test operation should succeed");
2752 let target = stats
2753 .objects
2754 .iter()
2755 .find(|entry| entry.oid == target_oid)
2756 .expect("target object should be present");
2757 assert_eq!(target.base_oid, Some(base_oid));
2758 }
2759
2760 fn write_packed_deltifies_similar_blobs_and_round_trips(format: ObjectFormat) {
2761 let objects = similar_blob_family(8);
2762 let packed =
2763 PackFile::write_packed(&objects, format).expect("test operation should succeed");
2764 let undeltified =
2765 PackFile::write_undeltified(&objects, format).expect("test operation should succeed");
2766
2767 assert!(
2770 packed.pack.len() < undeltified.pack.len(),
2771 "expected delta pack ({}) smaller than undeltified pack ({})",
2772 packed.pack.len(),
2773 undeltified.pack.len()
2774 );
2775
2776 let kinds = pack_entry_kinds(&packed.pack, format);
2778 let delta_count = kinds
2779 .iter()
2780 .filter(|kind| matches!(kind, PackObjectKind::OfsDelta | PackObjectKind::RefDelta))
2781 .count();
2782 assert!(
2783 delta_count >= 1,
2784 "expected at least one delta entry, found kinds {kinds:?}"
2785 );
2786
2787 let parsed = PackFile::parse(&packed.pack, format).expect("test operation should succeed");
2789 assert_eq!(parsed.entries.len(), objects.len());
2790 for object in &objects {
2791 let oid = object
2792 .object_id(format)
2793 .expect("test operation should succeed");
2794 let found = parsed
2795 .entries
2796 .iter()
2797 .find(|entry| entry.entry.oid == oid)
2798 .unwrap_or_else(|| panic!("object {oid} missing from parsed pack"));
2799 assert_eq!(&found.object, object, "object {oid} did not round-trip");
2800 }
2801
2802 let index = PackIndex::parse(&packed.index, format).expect("test operation should succeed");
2804 assert_eq!(index.pack_checksum, packed.checksum);
2805 for object in &objects {
2806 let oid = object
2807 .object_id(format)
2808 .expect("test operation should succeed");
2809 assert!(index.find(&oid).is_some(), "index missing {oid}");
2810 }
2811 }
2812
2813 #[test]
2814 fn write_packed_emits_ofs_delta_by_default() {
2815 let objects = similar_blob_family(6);
2816 let packed = PackFile::write_packed(&objects, ObjectFormat::Sha1)
2817 .expect("test operation should succeed");
2818 let kinds = pack_entry_kinds(&packed.pack, ObjectFormat::Sha1);
2819 assert!(
2820 kinds.contains(&PackObjectKind::OfsDelta),
2821 "expected an ofs-delta entry by default, found {kinds:?}"
2822 );
2823 assert!(
2824 !kinds.contains(&PackObjectKind::RefDelta),
2825 "default self-contained pack must not use ref-delta, found {kinds:?}"
2826 );
2827 assert!(PackFile::parse(&packed.pack, ObjectFormat::Sha1).is_ok());
2829 }
2830
2831 #[test]
2832 fn write_packed_can_emit_ref_delta() {
2833 let objects = similar_blob_family(6);
2834 let options = PackWriteOptions::new().with_prefer_ofs_delta(false);
2835 let packed = PackFile::write_packed_with_options(&objects, ObjectFormat::Sha1, &options)
2836 .expect("test operation should succeed");
2837 let kinds = pack_entry_kinds(&packed.pack, ObjectFormat::Sha1);
2838 assert!(
2839 kinds.contains(&PackObjectKind::RefDelta),
2840 "expected a ref-delta entry, found {kinds:?}"
2841 );
2842 assert!(
2843 !kinds.contains(&PackObjectKind::OfsDelta),
2844 "ref-delta mode must not emit ofs-delta, found {kinds:?}"
2845 );
2846
2847 let parsed = PackFile::parse(&packed.pack, ObjectFormat::Sha1)
2850 .expect("test operation should succeed");
2851 assert_eq!(parsed.entries.len(), objects.len());
2852 }
2853
2854 #[test]
2855 fn write_packed_bounds_delta_chain_depth() {
2856 let objects = incremental_blob_chain(20);
2860 let format = ObjectFormat::Sha1;
2861
2862 for max_depth in [1usize, 2, 5] {
2863 let options = PackWriteOptions::new()
2864 .with_window(20)
2865 .with_depth(max_depth);
2866 let packed = PackFile::write_packed_with_options(&objects, format, &options)
2867 .expect("test operation should succeed");
2868
2869 let depths = pack_entry_depths(&packed.pack, format);
2870 let observed = depths.iter().copied().max().unwrap_or(0);
2871 assert!(
2872 observed <= max_depth,
2873 "max chain depth {observed} exceeded bound {max_depth}"
2874 );
2875
2876 let parsed =
2878 PackFile::parse(&packed.pack, format).expect("test operation should succeed");
2879 for object in &objects {
2880 let oid = object
2881 .object_id(format)
2882 .expect("test operation should succeed");
2883 let found = parsed
2884 .entries
2885 .iter()
2886 .find(|entry| entry.entry.oid == oid)
2887 .expect("test operation should succeed");
2888 assert_eq!(&found.object, object);
2889 }
2890 }
2891 }
2892
2893 #[test]
2894 fn write_packed_depth_zero_stores_everything_undeltified() {
2895 let objects = similar_blob_family(5);
2896 let options = PackWriteOptions::new().with_depth(0);
2897 let packed = PackFile::write_packed_with_options(&objects, ObjectFormat::Sha1, &options)
2898 .expect("test operation should succeed");
2899 let kinds = pack_entry_kinds(&packed.pack, ObjectFormat::Sha1);
2900 assert!(
2901 kinds
2902 .iter()
2903 .all(|kind| !matches!(kind, PackObjectKind::OfsDelta | PackObjectKind::RefDelta)),
2904 "depth 0 must disable deltas, found {kinds:?}"
2905 );
2906 }
2907
2908 #[test]
2909 fn write_thin_uses_external_base_and_round_trips_sha1() {
2910 write_thin_uses_external_base_and_round_trips(ObjectFormat::Sha1);
2911 }
2912
2913 #[test]
2914 fn write_thin_uses_external_base_and_round_trips_sha256() {
2915 write_thin_uses_external_base_and_round_trips(ObjectFormat::Sha256);
2916 }
2917
2918 fn write_thin_uses_external_base_and_round_trips(format: ObjectFormat) {
2919 let base = blob_with_marker("EXTERNAL-BASE");
2922 let target = blob_with_marker("EXTERNAL-TARGET");
2923 let base_oid = base
2924 .object_id(format)
2925 .expect("test operation should succeed");
2926
2927 let mut external = HashMap::new();
2928 external.insert(base_oid, base.clone());
2929 let packed = PackFile::write_thin(std::slice::from_ref(&target), format, external)
2930 .expect("test operation should succeed");
2931
2932 let kinds = pack_entry_kinds(&packed.pack, format);
2934 assert_eq!(kinds, vec![PackObjectKind::RefDelta]);
2935
2936 let mut offset = 12usize;
2938 let header =
2939 parse_entry_header(&packed.pack, &mut offset).expect("test operation should succeed");
2940 assert_eq!(header.kind, PackObjectKind::RefDelta);
2941 let referenced =
2942 ObjectId::from_raw(format, &packed.pack[offset..offset + format.raw_len()])
2943 .expect("test operation should succeed");
2944 assert_eq!(referenced, base_oid);
2945
2946 assert!(PackFile::parse(&packed.pack, format).is_err());
2948
2949 let parsed = PackFile::parse_thin(&packed.pack, format, |oid| {
2951 if oid == &base_oid {
2952 Ok(Some(base.clone()))
2953 } else {
2954 Ok(None)
2955 }
2956 })
2957 .expect("test operation should succeed");
2958 assert_eq!(parsed.entries.len(), 1);
2959 assert_eq!(parsed.entries[0].object, target);
2960 }
2961
2962 #[test]
2963 fn write_packed_preserves_distinct_objects_with_no_similarity() {
2964 let objects = vec![
2967 EncodedObject::new(ObjectType::Blob, b"alpha distinct\n".to_vec()),
2968 EncodedObject::new(ObjectType::Tree, vec![0u8; 0]),
2969 EncodedObject::new(ObjectType::Commit, b"tree 0000\n".to_vec()),
2970 ];
2971 let format = ObjectFormat::Sha1;
2972 let packed =
2973 PackFile::write_packed(&objects, format).expect("test operation should succeed");
2974 let parsed = PackFile::parse(&packed.pack, format).expect("test operation should succeed");
2975 assert_eq!(parsed.entries.len(), objects.len());
2976 for object in &objects {
2977 let oid = object
2978 .object_id(format)
2979 .expect("test operation should succeed");
2980 assert!(parsed.entries.iter().any(|entry| entry.entry.oid == oid));
2981 }
2982 }
2983
2984 fn similar_blob_family(count: usize) -> Vec<EncodedObject> {
2988 let mut common_head = Vec::new();
2989 for _ in 0..200 {
2990 common_head.extend_from_slice(b"shared header line for delta testing\n");
2991 }
2992 let mut common_tail = Vec::new();
2993 for _ in 0..200 {
2994 common_tail.extend_from_slice(b"shared trailer line for delta testing\n");
2995 }
2996 (0..count)
2997 .map(|idx| {
2998 let mut body = common_head.clone();
2999 body.extend_from_slice(format!("UNIQUE MIDDLE MARKER NUMBER {idx}\n").as_bytes());
3000 body.extend_from_slice(&common_tail);
3001 EncodedObject::new(ObjectType::Blob, body)
3002 })
3003 .collect()
3004 }
3005
3006 fn incremental_blob_chain(count: usize) -> Vec<EncodedObject> {
3009 let mut body = Vec::new();
3010 for _ in 0..100 {
3011 body.extend_from_slice(b"baseline content shared across the whole chain\n");
3012 }
3013 let mut objects = Vec::with_capacity(count);
3014 for idx in 0..count {
3015 body.extend_from_slice(format!("appended unique line {idx}\n").as_bytes());
3016 objects.push(EncodedObject::new(ObjectType::Blob, body.clone()));
3017 }
3018 objects
3019 }
3020
3021 fn blob_with_marker(marker: &str) -> EncodedObject {
3022 let mut body = Vec::new();
3023 for _ in 0..150 {
3024 body.extend_from_slice(b"common body shared between base and target\n");
3025 }
3026 body.extend_from_slice(marker.as_bytes());
3027 body.push(b'\n');
3028 for _ in 0..150 {
3029 body.extend_from_slice(b"more common body shared between objects\n");
3030 }
3031 EncodedObject::new(ObjectType::Blob, body)
3032 }
3033
3034 fn pack_entry_kinds(pack: &[u8], format: ObjectFormat) -> Vec<PackObjectKind> {
3036 pack_entry_descriptors(pack, format)
3037 .into_iter()
3038 .map(|descriptor| descriptor.kind)
3039 .collect()
3040 }
3041
3042 fn pack_entry_depths(pack: &[u8], format: ObjectFormat) -> Vec<usize> {
3046 let descriptors = pack_entry_descriptors(pack, format);
3047 let mut depth_by_offset: HashMap<u64, usize> = HashMap::new();
3048 let mut depths = Vec::with_capacity(descriptors.len());
3049 for descriptor in &descriptors {
3050 let depth = match &descriptor.base {
3051 EntryBase::None => 0,
3052 EntryBase::Offset(base_offset) => {
3053 depth_by_offset.get(base_offset).copied().unwrap_or(0) + 1
3054 }
3055 EntryBase::Ref => 1,
3059 };
3060 depth_by_offset.insert(descriptor.offset, depth);
3061 depths.push(depth);
3062 }
3063 depths
3064 }
3065
3066 struct EntryDescriptor {
3067 offset: u64,
3068 kind: PackObjectKind,
3069 base: EntryBase,
3070 }
3071
3072 enum EntryBase {
3073 None,
3074 Offset(u64),
3075 Ref,
3076 }
3077
3078 fn pack_entry_descriptors(pack: &[u8], format: ObjectFormat) -> Vec<EntryDescriptor> {
3079 let trailer_offset = pack.len() - format.raw_len();
3080 let count = u32_be(&pack[8..12]) as usize;
3081 let mut offset = 12usize;
3082 let mut descriptors = Vec::with_capacity(count);
3083 for _ in 0..count {
3084 let entry_offset = offset as u64;
3085 let header =
3086 parse_entry_header(pack, &mut offset).expect("test operation should succeed");
3087 let base = match header.kind {
3088 PackObjectKind::OfsDelta => {
3089 let base_offset = parse_ofs_delta_base_offset(pack, &mut offset, entry_offset)
3090 .expect("test operation should succeed");
3091 EntryBase::Offset(base_offset)
3092 }
3093 PackObjectKind::RefDelta => {
3094 offset += format.raw_len();
3095 EntryBase::Ref
3096 }
3097 _ => EntryBase::None,
3098 };
3099 let mut decoder = ZlibDecoder::new(&pack[offset..trailer_offset]);
3100 let mut body = Vec::new();
3101 decoder
3102 .read_to_end(&mut body)
3103 .expect("test operation should succeed");
3104 offset += decoder.total_in() as usize;
3105 descriptors.push(EntryDescriptor {
3106 offset: entry_offset,
3107 kind: header.kind,
3108 base,
3109 });
3110 }
3111 descriptors
3112 }
3113
3114 fn similar_blob_objects() -> (EncodedObject, EncodedObject) {
3115 let mut base = Vec::new();
3116 for _ in 0..300 {
3117 base.extend_from_slice(b"common payload\n");
3118 }
3119 base.extend_from_slice(b"base\n");
3120 let mut changed = Vec::new();
3121 for _ in 0..300 {
3122 changed.extend_from_slice(b"common payload\n");
3123 }
3124 changed.extend_from_slice(b"changed\n");
3125 (
3126 EncodedObject::new(ObjectType::Blob, base),
3127 EncodedObject::new(ObjectType::Blob, changed),
3128 )
3129 }
3130
3131 fn single_object_pack(format: ObjectFormat, object_type: ObjectType, body: &[u8]) -> Vec<u8> {
3132 let mut pack = Vec::new();
3133 pack.extend_from_slice(b"PACK");
3134 pack.extend_from_slice(&2u32.to_be_bytes());
3135 pack.extend_from_slice(&1u32.to_be_bytes());
3136 write_entry_header(&mut pack, object_type, body.len() as u64);
3137 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
3138 encoder
3139 .write_all(body)
3140 .expect("test operation should succeed");
3141 pack.extend_from_slice(&encoder.finish().expect("test operation should succeed"));
3142 let checksum =
3143 sley_core::digest_bytes(format, &pack).expect("test operation should succeed");
3144 pack.extend_from_slice(checksum.as_bytes());
3145 pack
3146 }
3147
3148 #[derive(Clone, Copy, Debug)]
3149 enum DeltaKind {
3150 Offset,
3151 Ref,
3152 }
3153
3154 fn two_object_delta_pack(
3155 format: ObjectFormat,
3156 base: &[u8],
3157 result: &[u8],
3158 delta_kind: DeltaKind,
3159 ) -> Vec<u8> {
3160 let mut pack = Vec::new();
3161 pack.extend_from_slice(b"PACK");
3162 pack.extend_from_slice(&2u32.to_be_bytes());
3163 pack.extend_from_slice(&2u32.to_be_bytes());
3164
3165 let base_offset = pack.len();
3166 write_entry_header(&mut pack, ObjectType::Blob, base.len() as u64);
3167 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
3168 encoder
3169 .write_all(base)
3170 .expect("test operation should succeed");
3171 pack.extend_from_slice(&encoder.finish().expect("test operation should succeed"));
3172
3173 let delta = append_suffix_delta(base, result);
3174 let delta_offset = pack.len();
3175 write_pack_entry_header_kind(
3176 &mut pack,
3177 match delta_kind {
3178 DeltaKind::Offset => 6,
3179 DeltaKind::Ref => 7,
3180 },
3181 delta.len() as u64,
3182 );
3183 match delta_kind {
3184 DeltaKind::Offset => write_ofs_delta_offset(&mut pack, delta_offset - base_offset),
3185 DeltaKind::Ref => {
3186 let base_oid = sley_core::object_id_for_bytes(format, "blob", base)
3187 .expect("test operation should succeed");
3188 pack.extend_from_slice(base_oid.as_bytes());
3189 }
3190 }
3191 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
3192 encoder
3193 .write_all(&delta)
3194 .expect("test operation should succeed");
3195 pack.extend_from_slice(&encoder.finish().expect("test operation should succeed"));
3196
3197 let checksum =
3198 sley_core::digest_bytes(format, &pack).expect("test operation should succeed");
3199 pack.extend_from_slice(checksum.as_bytes());
3200 pack
3201 }
3202
3203 fn thin_ref_delta_pack(format: ObjectFormat, base: &[u8], result: &[u8]) -> Vec<u8> {
3204 let mut pack = Vec::new();
3205 pack.extend_from_slice(b"PACK");
3206 pack.extend_from_slice(&2u32.to_be_bytes());
3207 pack.extend_from_slice(&1u32.to_be_bytes());
3208
3209 let delta = append_suffix_delta(base, result);
3210 write_pack_entry_header_kind(&mut pack, 7, delta.len() as u64);
3211 let base_oid = sley_core::object_id_for_bytes(format, "blob", base)
3212 .expect("test operation should succeed");
3213 pack.extend_from_slice(base_oid.as_bytes());
3214 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
3215 encoder
3216 .write_all(&delta)
3217 .expect("test operation should succeed");
3218 pack.extend_from_slice(&encoder.finish().expect("test operation should succeed"));
3219
3220 let checksum =
3221 sley_core::digest_bytes(format, &pack).expect("test operation should succeed");
3222 pack.extend_from_slice(checksum.as_bytes());
3223 pack
3224 }
3225
3226 fn thin_ref_delta_pack_many(format: ObjectFormat, base: &[u8], results: &[&[u8]]) -> Vec<u8> {
3227 let mut pack = Vec::new();
3228 pack.extend_from_slice(b"PACK");
3229 pack.extend_from_slice(&2u32.to_be_bytes());
3230 pack.extend_from_slice(&(results.len() as u32).to_be_bytes());
3231 let base_oid = sley_core::object_id_for_bytes(format, "blob", base)
3232 .expect("test operation should succeed");
3233 for result in results {
3234 let delta = append_suffix_delta(base, result);
3235 write_pack_entry_header_kind(&mut pack, 7, delta.len() as u64);
3236 pack.extend_from_slice(base_oid.as_bytes());
3237 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
3238 encoder
3239 .write_all(&delta)
3240 .expect("test operation should succeed");
3241 pack.extend_from_slice(&encoder.finish().expect("test operation should succeed"));
3242 }
3243 let checksum =
3244 sley_core::digest_bytes(format, &pack).expect("test operation should succeed");
3245 pack.extend_from_slice(checksum.as_bytes());
3246 pack
3247 }
3248
3249 fn unique_temp_dir(name: &str) -> PathBuf {
3250 let nanos = SystemTime::now()
3251 .duration_since(UNIX_EPOCH)
3252 .expect("test operation should succeed")
3253 .as_nanos();
3254 std::env::temp_dir().join(format!("sley-{name}-{}-{nanos}", std::process::id()))
3255 }
3256
3257 fn run_git_success(cwd: &Path, args: &[&str]) {
3258 let output = Command::new("git")
3259 .current_dir(cwd)
3260 .args(args)
3261 .output()
3262 .unwrap_or_else(|err| panic!("failed to run git {args:?}: {err}"));
3263 assert!(
3264 output.status.success(),
3265 "git {args:?} failed with status {:?}\nstdout:\n{}\nstderr:\n{}",
3266 output.status.code(),
3267 String::from_utf8_lossy(&output.stdout),
3268 String::from_utf8_lossy(&output.stderr)
3269 );
3270 }
3271
3272 fn single_path_with_extension(dir: &Path, extension: &str) -> PathBuf {
3273 let mut paths = fs::read_dir(dir)
3274 .expect("test operation should succeed")
3275 .map(|entry| entry.expect("test operation should succeed").path())
3276 .filter(|path| path.extension().and_then(|ext| ext.to_str()) == Some(extension))
3277 .collect::<Vec<_>>();
3278 assert_eq!(paths.len(), 1, "expected one .{extension} file");
3279 paths.remove(0)
3280 }
3281
3282 fn largest_path_with_extension(dir: &Path, extension: &str) -> PathBuf {
3283 fs::read_dir(dir)
3284 .expect("read fixture directory")
3285 .map(|entry| entry.expect("read fixture entry").path())
3286 .filter(|path| path.extension().and_then(|ext| ext.to_str()) == Some(extension))
3287 .max_by_key(|path| fs::metadata(path).expect("read fixture metadata").len())
3288 .expect("at least one fixture path")
3289 }
3290
3291 fn pack_bitmap_index(
3292 format: ObjectFormat,
3293 object_count: u32,
3294 options: u16,
3295 pack_checksum: &ObjectId,
3296 entries: &[(u32, u8, u8, &[u64])],
3297 name_hash_cache: Option<&[u32]>,
3298 ) -> Vec<u8> {
3299 let mut out = Vec::new();
3300 out.extend_from_slice(b"BITM");
3301 out.extend_from_slice(&1u16.to_be_bytes());
3302 out.extend_from_slice(&options.to_be_bytes());
3303 out.extend_from_slice(&(entries.len() as u32).to_be_bytes());
3304 out.extend_from_slice(pack_checksum.as_bytes());
3305 write_test_ewah(&mut out, object_count, &[0b001]);
3306 write_test_ewah(&mut out, object_count, &[0b010]);
3307 write_test_ewah(&mut out, object_count, &[0b100]);
3308 write_test_ewah(&mut out, object_count, &[0]);
3309 for (position, xor_offset, flags, words) in entries {
3310 out.extend_from_slice(&position.to_be_bytes());
3311 out.push(*xor_offset);
3312 out.push(*flags);
3313 write_test_ewah(&mut out, object_count, words);
3314 }
3315 if let Some(cache) = name_hash_cache {
3316 for value in cache {
3317 out.extend_from_slice(&value.to_be_bytes());
3318 }
3319 }
3320 let checksum =
3321 sley_core::digest_bytes(format, &out).expect("test operation should succeed");
3322 out.extend_from_slice(checksum.as_bytes());
3323 out
3324 }
3325
3326 fn write_test_ewah(out: &mut Vec<u8>, bit_size: u32, literals: &[u64]) {
3327 out.extend_from_slice(&bit_size.to_be_bytes());
3328 let words = ewah_literal_words(literals);
3329 out.extend_from_slice(&(words.len() as u32).to_be_bytes());
3330 for word in words {
3331 out.extend_from_slice(&word.to_be_bytes());
3332 }
3333 out.extend_from_slice(&0u32.to_be_bytes());
3334 }
3335
3336 fn ewah_literal_words(literals: &[u64]) -> Vec<u64> {
3337 let rlw = (literals.len() as u64) << 33;
3338 let mut words = vec![rlw];
3339 words.extend_from_slice(literals);
3340 words
3341 }
3342
3343 fn refresh_trailing_checksum(format: ObjectFormat, bytes: &mut [u8]) {
3344 let checksum_offset = bytes.len() - format.raw_len();
3345 let checksum = sley_core::digest_bytes(format, &bytes[..checksum_offset])
3346 .expect("test operation should succeed");
3347 bytes[checksum_offset..].copy_from_slice(checksum.as_bytes());
3348 }
3349
3350 fn append_suffix_delta(base: &[u8], result: &[u8]) -> Vec<u8> {
3351 assert!(result.starts_with(base));
3352 let suffix = &result[base.len()..];
3353 assert!(base.len() < 0x10000);
3354 assert!(suffix.len() < 0x80);
3355 let mut delta = Vec::new();
3356 write_delta_varint(&mut delta, base.len() as u64);
3357 write_delta_varint(&mut delta, result.len() as u64);
3358 delta.push(0x90);
3359 delta.push(base.len() as u8);
3360 delta.push(suffix.len() as u8);
3361 delta.extend_from_slice(suffix);
3362 delta
3363 }
3364
3365 fn write_delta_varint(out: &mut Vec<u8>, mut value: u64) {
3366 loop {
3367 let mut byte = (value as u8) & 0x7f;
3368 value >>= 7;
3369 if value != 0 {
3370 byte |= 0x80;
3371 }
3372 out.push(byte);
3373 if value == 0 {
3374 break;
3375 }
3376 }
3377 }
3378
3379 fn write_pack_entry_header_kind(out: &mut Vec<u8>, type_code: u8, mut size: u64) {
3380 let mut byte = (type_code << 4) | ((size as u8) & 0x0f);
3381 size >>= 4;
3382 if size != 0 {
3383 byte |= 0x80;
3384 }
3385 out.push(byte);
3386 while size != 0 {
3387 let mut byte = (size as u8) & 0x7f;
3388 size >>= 7;
3389 if size != 0 {
3390 byte |= 0x80;
3391 }
3392 out.push(byte);
3393 }
3394 }
3395
3396 fn write_ofs_delta_offset(out: &mut Vec<u8>, relative: usize) {
3397 assert!(relative < 0x80);
3398 out.push(relative as u8);
3399 }
3400
3401 fn single_entry_index(
3402 format: ObjectFormat,
3403 oid: ObjectId,
3404 crc32: u32,
3405 offset: u32,
3406 pack_checksum: ObjectId,
3407 ) -> Vec<u8> {
3408 let mut index = Vec::new();
3409 index.extend_from_slice(&[0xff, b't', b'O', b'c']);
3410 index.extend_from_slice(&2u32.to_be_bytes());
3411 for idx in 0..256 {
3412 let count = if idx >= usize::from(oid.as_bytes()[0]) {
3413 1u32
3414 } else {
3415 0u32
3416 };
3417 index.extend_from_slice(&count.to_be_bytes());
3418 }
3419 index.extend_from_slice(oid.as_bytes());
3420 index.extend_from_slice(&crc32.to_be_bytes());
3421 index.extend_from_slice(&offset.to_be_bytes());
3422 index.extend_from_slice(pack_checksum.as_bytes());
3423 let checksum =
3424 sley_core::digest_bytes(format, &index).expect("test operation should succeed");
3425 index.extend_from_slice(checksum.as_bytes());
3426 index
3427 }
3428
3429 fn single_entry_index_v1(
3430 format: ObjectFormat,
3431 oid: ObjectId,
3432 offset: u32,
3433 pack_checksum: ObjectId,
3434 ) -> Vec<u8> {
3435 let mut index = Vec::new();
3436 for idx in 0..256 {
3437 let count = if idx >= usize::from(oid.as_bytes()[0]) {
3438 1u32
3439 } else {
3440 0u32
3441 };
3442 index.extend_from_slice(&count.to_be_bytes());
3443 }
3444 index.extend_from_slice(&offset.to_be_bytes());
3445 index.extend_from_slice(oid.as_bytes());
3446 index.extend_from_slice(pack_checksum.as_bytes());
3447 let checksum =
3448 sley_core::digest_bytes(format, &index).expect("test operation should succeed");
3449 index.extend_from_slice(checksum.as_bytes());
3450 index
3451 }
3452
3453 fn pack_reverse_index(
3454 format: ObjectFormat,
3455 positions: &[u32],
3456 pack_checksum: ObjectId,
3457 ) -> Vec<u8> {
3458 let mut reverse_index = Vec::new();
3459 reverse_index.extend_from_slice(b"RIDX");
3460 reverse_index.extend_from_slice(&1u32.to_be_bytes());
3461 reverse_index.extend_from_slice(&hash_function_id(format).to_be_bytes());
3462 for position in positions {
3463 reverse_index.extend_from_slice(&position.to_be_bytes());
3464 }
3465 reverse_index.extend_from_slice(pack_checksum.as_bytes());
3466 let checksum =
3467 sley_core::digest_bytes(format, &reverse_index).expect("test operation should succeed");
3468 reverse_index.extend_from_slice(checksum.as_bytes());
3469 reverse_index
3470 }
3471
3472 fn pack_mtimes(format: ObjectFormat, mtimes: &[u32], pack_checksum: ObjectId) -> Vec<u8> {
3473 let mut out = Vec::new();
3474 out.extend_from_slice(b"MTME");
3475 out.extend_from_slice(&1u32.to_be_bytes());
3476 out.extend_from_slice(&hash_function_id(format).to_be_bytes());
3477 for mtime in mtimes {
3478 out.extend_from_slice(&mtime.to_be_bytes());
3479 }
3480 out.extend_from_slice(pack_checksum.as_bytes());
3481 let checksum =
3482 sley_core::digest_bytes(format, &out).expect("test operation should succeed");
3483 out.extend_from_slice(checksum.as_bytes());
3484 out
3485 }
3486
3487 fn midx_chunks_with_pack_names(
3488 _format: ObjectFormat,
3489 pack_names: Vec<u8>,
3490 entries: &[(ObjectId, u32, u64)],
3491 ) -> Vec<([u8; 4], Vec<u8>)> {
3492 let mut entries = entries.to_vec();
3493 entries.sort_by(|left, right| left.0.as_bytes().cmp(right.0.as_bytes()));
3494 let object_ids: Vec<ObjectId> = entries.iter().map(|entry| entry.0).collect();
3495 let mut large_offsets = Vec::new();
3496 let mut chunks = vec![
3497 (*b"PNAM", pack_names),
3498 (*b"OIDF", midx_oid_fanout(&object_ids)),
3499 (*b"OIDL", midx_oid_lookup(&object_ids)),
3500 (
3501 *b"OOFF",
3502 midx_ooff_entries(
3503 &entries
3504 .iter()
3505 .map(|(_oid, pack_int_id, offset)| (*pack_int_id, *offset))
3506 .collect::<Vec<_>>(),
3507 &mut large_offsets,
3508 ),
3509 ),
3510 ];
3511 if !large_offsets.is_empty() {
3512 chunks.push((*b"LOFF", large_offsets));
3513 }
3514 chunks
3515 }
3516
3517 fn midx_oid_fanout(object_ids: &[ObjectId]) -> Vec<u8> {
3518 let mut counts = [0u32; 256];
3519 for oid in object_ids {
3520 counts[oid.as_bytes()[0] as usize] += 1;
3521 }
3522 let mut running = 0u32;
3523 let mut out = Vec::new();
3524 for count in counts {
3525 running += count;
3526 out.extend_from_slice(&running.to_be_bytes());
3527 }
3528 out
3529 }
3530
3531 fn midx_oid_lookup(object_ids: &[ObjectId]) -> Vec<u8> {
3532 let mut out = Vec::new();
3533 for oid in object_ids {
3534 out.extend_from_slice(oid.as_bytes());
3535 }
3536 out
3537 }
3538
3539 fn midx_ooff_entries(entries: &[(u32, u64)], large_offsets: &mut Vec<u8>) -> Vec<u8> {
3540 let mut out = Vec::new();
3541 for (pack_int_id, offset) in entries {
3542 out.extend_from_slice(&pack_int_id.to_be_bytes());
3543 if *offset < 0x8000_0000 {
3544 out.extend_from_slice(&(*offset as u32).to_be_bytes());
3545 } else {
3546 let large_idx = (large_offsets.len() / 8) as u32;
3547 out.extend_from_slice(&(0x8000_0000 | large_idx).to_be_bytes());
3548 large_offsets.extend_from_slice(&offset.to_be_bytes());
3549 }
3550 }
3551 out
3552 }
3553
3554 fn midx_u32_table(values: &[u32]) -> Vec<u8> {
3555 let mut out = Vec::new();
3556 for value in values {
3557 out.extend_from_slice(&value.to_be_bytes());
3558 }
3559 out
3560 }
3561
3562 fn midx_bitmap_packs(entries: &[(u32, u32)]) -> Vec<u8> {
3563 let mut out = Vec::new();
3564 for (bitmap_pos, bitmap_nr) in entries {
3565 out.extend_from_slice(&bitmap_pos.to_be_bytes());
3566 out.extend_from_slice(&bitmap_nr.to_be_bytes());
3567 }
3568 out
3569 }
3570
3571 fn multi_pack_index(
3572 format: ObjectFormat,
3573 version: u8,
3574 pack_count: u32,
3575 chunks: &[([u8; 4], Vec<u8>)],
3576 ) -> Vec<u8> {
3577 let lookup_len = (chunks.len() + 1) * 12;
3578 let mut out = Vec::new();
3579 out.extend_from_slice(b"MIDX");
3580 out.push(version);
3581 out.push(hash_function_id(format) as u8);
3582 out.push(chunks.len() as u8);
3583 out.push(0);
3584 out.extend_from_slice(&pack_count.to_be_bytes());
3585 let mut chunk_offset = (12 + lookup_len) as u64;
3586 for (id, data) in chunks {
3587 out.extend_from_slice(id);
3588 out.extend_from_slice(&chunk_offset.to_be_bytes());
3589 chunk_offset += data.len() as u64;
3590 }
3591 out.extend_from_slice(&[0, 0, 0, 0]);
3592 out.extend_from_slice(&chunk_offset.to_be_bytes());
3593 for (_id, data) in chunks {
3594 out.extend_from_slice(data);
3595 }
3596 let checksum =
3597 sley_core::digest_bytes(format, &out).expect("test operation should succeed");
3598 out.extend_from_slice(checksum.as_bytes());
3599 out
3600 }
3601
3602 fn pack_checksum_sha1() -> ObjectId {
3605 sley_core::digest_bytes(ObjectFormat::Sha1, b"pack").expect("test operation should succeed")
3606 }
3607
3608 fn parse_ewah_bytes(bytes: &[u8]) -> EwahBitmap {
3609 let mut offset = 0usize;
3612 let checksum_offset = bytes.len();
3613 parse_bitmap_ewah(bytes, &mut offset, checksum_offset, 0)
3614 .expect("test operation should succeed")
3615 }
3616
3617 #[test]
3618 fn ewah_encodes_single_literal_word_matching_helper() {
3619 let ewah = EwahBitmap::from_words(64, &[0b101]).expect("test operation should succeed");
3623 assert_eq!(ewah.words, ewah_literal_words(&[0b101]));
3624 assert_eq!(ewah.rlw_position, 0);
3625 assert_eq!(ewah.bit_size, 64);
3626 }
3627
3628 #[test]
3629 fn ewah_byte_layout_is_big_endian() {
3630 let ewah = EwahBitmap::from_words(64, &[0x0102_0304_0506_0708])
3631 .expect("test operation should succeed");
3632 let bytes = ewah.to_bytes();
3633 let mut expected = Vec::new();
3634 expected.extend_from_slice(&64u32.to_be_bytes()); expected.extend_from_slice(&2u32.to_be_bytes()); expected.extend_from_slice(&(1u64 << 33).to_be_bytes()); expected.extend_from_slice(&0x0102_0304_0506_0708u64.to_be_bytes());
3638 expected.extend_from_slice(&0u32.to_be_bytes()); assert_eq!(bytes, expected);
3640 }
3641
3642 #[test]
3643 fn ewah_empty_bitmap_serialises_like_git() {
3644 let ewah = EwahBitmap::empty();
3645 let bytes = ewah.to_bytes();
3646 assert_eq!(bytes, vec![0u8; 12]);
3648 let parsed = parse_ewah_bytes(&bytes);
3650 assert_eq!(parsed, ewah);
3651 assert!(
3652 parsed
3653 .to_positions()
3654 .expect("test operation should succeed")
3655 .is_empty()
3656 );
3657 }
3658
3659 #[test]
3660 fn ewah_compresses_clean_zero_run() {
3661 let ewah =
3664 EwahBitmap::from_words(256, &[0, 0, 0, 0b1]).expect("test operation should succeed");
3665 assert_eq!(ewah.words.len(), 2, "expected one RLW plus one literal");
3666 let rlw = ewah.words[0];
3667 assert_eq!(rlw & 1, 0, "run bit should be zero");
3668 assert_eq!((rlw >> 1) & 0xffff_ffff, 3, "run length should be 3");
3669 assert_eq!(rlw >> 33, 1, "literal length should be 1");
3670 assert_eq!(ewah.words[1], 0b1);
3671 }
3672
3673 #[test]
3674 fn ewah_compresses_clean_ones_run() {
3675 let ewah = EwahBitmap::from_words(192, &[u64::MAX, u64::MAX, u64::MAX])
3676 .expect("test operation should succeed");
3677 assert_eq!(ewah.words.len(), 1);
3679 let rlw = ewah.words[0];
3680 assert_eq!(rlw & 1, 1, "run bit should be one");
3681 assert_eq!((rlw >> 1) & 0xffff_ffff, 3, "run length should be 3");
3682 assert_eq!(rlw >> 33, 0, "no literals");
3683 }
3684
3685 #[test]
3686 fn ewah_run_then_literal_then_run_roundtrips() {
3687 let words = vec![0, 0, 0xdead_beef, u64::MAX, u64::MAX, 0, 0xabc];
3688 let bit_size = (words.len() * 64) as u32;
3689 let ewah = EwahBitmap::from_words(bit_size, &words).expect("test operation should succeed");
3690 assert_eq!(
3691 ewah.to_words().expect("test operation should succeed"),
3692 words
3693 );
3694 }
3695
3696 #[test]
3697 fn ewah_drops_trailing_clean_zero_words() {
3698 let words = vec![0b1, 0, 0, 0];
3701 let ewah = EwahBitmap::from_words(1, &words).expect("test operation should succeed");
3702 assert_eq!(ewah.bit_size, 1);
3704 assert_eq!(
3705 ewah.to_words().expect("test operation should succeed"),
3706 vec![0b1]
3707 );
3708 }
3709
3710 #[test]
3711 fn ewah_from_positions_roundtrips_via_positions() {
3712 let positions = [0u32, 1, 63, 64, 65, 200, 511];
3713 let ewah =
3714 EwahBitmap::from_positions(512, &positions).expect("test operation should succeed");
3715 let mut decoded = ewah.to_positions().expect("test operation should succeed");
3716 decoded.sort_unstable();
3717 assert_eq!(decoded, positions);
3718 }
3719
3720 #[test]
3721 fn ewah_from_positions_dedupes_and_orders() {
3722 let ewah = EwahBitmap::from_positions(128, &[100, 5, 100, 5, 5])
3723 .expect("test operation should succeed");
3724 assert_eq!(
3725 ewah.to_positions().expect("test operation should succeed"),
3726 vec![5, 100]
3727 );
3728 }
3729
3730 #[test]
3731 fn ewah_huge_zero_run_spans_multiple_rlws() {
3732 let mut builder = EwahBuilder::new(0);
3737 builder.add_empty_words(false, 0xffff_ffff);
3738 builder.add_empty_words(false, 5);
3739 let ewah = builder.finish().expect("test operation should succeed");
3740 assert_eq!(ewah.words.len(), 2, "run split across two RLWs");
3741 assert_eq!((ewah.words[0] >> 1) & 0xffff_ffff, 0xffff_ffff);
3742 assert_eq!(ewah.words[1] & 1, 0);
3743 assert_eq!((ewah.words[1] >> 1) & 0xffff_ffff, 5);
3744 assert_eq!(ewah.rlw_position, 1);
3745 }
3746
3747 #[test]
3748 fn ewah_from_words_rejects_oversized_bit_size() {
3749 assert!(EwahBitmap::from_words(65, &[0]).is_err());
3751 }
3752
3753 #[test]
3754 fn ewah_from_positions_rejects_out_of_range() {
3755 assert!(EwahBitmap::from_positions(64, &[64]).is_err());
3756 }
3757
3758 #[test]
3759 fn ewah_serialised_bytes_reparse_to_equal_bitmap() {
3760 let words = vec![0, u64::MAX, 0x1234_5678_9abc_def0, 0, 0, 0xff];
3763 let bit_size = (words.len() * 64) as u32;
3764 let ewah = EwahBitmap::from_words(bit_size, &words).expect("test operation should succeed");
3765 let bytes = ewah.to_bytes();
3766 let parsed = parse_ewah_bytes(&bytes);
3767 assert_eq!(parsed, ewah);
3768 assert_eq!(
3769 parsed.to_words().expect("test operation should succeed"),
3770 words
3771 );
3772 }
3773
3774 #[test]
3775 fn pack_bitmap_index_write_parse_roundtrip_sha1() {
3776 let object_types = [ObjectType::Commit, ObjectType::Tree, ObjectType::Blob];
3778 let bytes = write_bitmap(
3779 ObjectFormat::Sha1,
3780 pack_checksum_sha1(),
3781 &object_types,
3782 &[(0u32, 0u32, vec![1u32, 2u32])],
3783 None,
3784 )
3785 .expect("test operation should succeed");
3786 assert_eq!(&bytes[..4], b"BITM");
3787
3788 let parsed = PackBitmapIndex::parse(&bytes, ObjectFormat::Sha1, 3)
3789 .expect("test operation should succeed");
3790 assert_eq!(parsed.version, 1);
3791 assert_eq!(parsed.options, PackBitmapIndex::OPTION_FULL_DAG);
3792 assert_eq!(parsed.pack_checksum, pack_checksum_sha1());
3793 assert_eq!(
3794 parsed
3795 .type_bitmaps
3796 .commits
3797 .to_positions()
3798 .expect("test operation should succeed"),
3799 vec![0]
3800 );
3801 assert_eq!(
3802 parsed
3803 .type_bitmaps
3804 .trees
3805 .to_positions()
3806 .expect("test operation should succeed"),
3807 vec![1]
3808 );
3809 assert_eq!(
3810 parsed
3811 .type_bitmaps
3812 .blobs
3813 .to_positions()
3814 .expect("test operation should succeed"),
3815 vec![2]
3816 );
3817 assert!(
3818 parsed
3819 .type_bitmaps
3820 .tags
3821 .to_positions()
3822 .expect("test operation should succeed")
3823 .is_empty()
3824 );
3825 assert_eq!(parsed.entries.len(), 1);
3826 let entry = parsed
3827 .entry_for_index_position(0)
3828 .expect("test operation should succeed");
3829 assert_eq!(entry.xor_offset, 0);
3830 assert_eq!(entry.flags, 0);
3831 assert_eq!(
3832 entry
3833 .bitmap
3834 .to_positions()
3835 .expect("test operation should succeed"),
3836 vec![0, 1, 2]
3837 );
3838 assert_eq!(parsed.name_hash_cache, None);
3839 }
3840
3841 #[test]
3842 fn pack_bitmap_index_write_parse_roundtrip_sha256() {
3843 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha256, b"pack")
3844 .expect("test operation should succeed");
3845 let object_types = [ObjectType::Commit, ObjectType::Tree];
3846 let bytes = write_bitmap(
3847 ObjectFormat::Sha256,
3848 pack_checksum.clone(),
3849 &object_types,
3850 &[(0u32, 0u32, vec![1u32])],
3851 None,
3852 )
3853 .expect("test operation should succeed");
3854 let parsed = PackBitmapIndex::parse(&bytes, ObjectFormat::Sha256, 2)
3855 .expect("test operation should succeed");
3856 assert_eq!(parsed.format, ObjectFormat::Sha256);
3857 assert_eq!(parsed.pack_checksum, pack_checksum);
3858 assert_eq!(parsed.index_checksum.format(), ObjectFormat::Sha256);
3859 assert_eq!(
3860 parsed.entries[0]
3861 .bitmap
3862 .to_positions()
3863 .expect("test operation should succeed"),
3864 vec![0, 1]
3865 );
3866 }
3867
3868 #[test]
3869 fn pack_bitmap_index_write_includes_name_hash_cache() {
3870 let object_types = [ObjectType::Commit, ObjectType::Tree, ObjectType::Blob];
3871 let cache = vec![0x1111_1111u32, 0x2222_2222, 0x3333_3333];
3872 let bytes = write_bitmap(
3873 ObjectFormat::Sha1,
3874 pack_checksum_sha1(),
3875 &object_types,
3876 &[(0u32, 0u32, vec![1u32, 2u32])],
3877 Some(cache.clone()),
3878 )
3879 .expect("test operation should succeed");
3880 let parsed = PackBitmapIndex::parse(&bytes, ObjectFormat::Sha1, 3)
3881 .expect("test operation should succeed");
3882 assert_eq!(
3883 parsed.options,
3884 PackBitmapIndex::OPTION_FULL_DAG | PackBitmapIndex::OPTION_HASH_CACHE
3885 );
3886 assert_eq!(parsed.name_hash_cache, Some(cache));
3887 }
3888
3889 #[test]
3890 fn pack_bitmap_writer_supports_multiple_commits() {
3891 let object_types = [
3892 ObjectType::Commit,
3893 ObjectType::Commit,
3894 ObjectType::Tree,
3895 ObjectType::Blob,
3896 ];
3897 let mut writer =
3898 PackBitmapWriter::new(ObjectFormat::Sha1, pack_checksum_sha1(), &object_types)
3899 .expect("test operation should succeed");
3900 writer
3901 .add_commit(0, 0, &[2, 3])
3902 .expect("test operation should succeed");
3903 writer
3904 .add_commit(1, 1, &[2])
3905 .expect("test operation should succeed");
3906 let bytes = writer.write().expect("test operation should succeed");
3907 let parsed = PackBitmapIndex::parse(&bytes, ObjectFormat::Sha1, 4)
3908 .expect("test operation should succeed");
3909 assert_eq!(parsed.entries.len(), 2);
3910 assert_eq!(
3911 parsed
3912 .type_bitmaps
3913 .commits
3914 .to_positions()
3915 .expect("test operation should succeed"),
3916 vec![0, 1]
3917 );
3918 let first = parsed
3919 .entry_for_index_position(0)
3920 .expect("test operation should succeed");
3921 assert_eq!(
3922 first
3923 .bitmap
3924 .to_positions()
3925 .expect("test operation should succeed"),
3926 vec![0, 2, 3]
3927 );
3928 let second = parsed
3929 .entry_for_index_position(1)
3930 .expect("test operation should succeed");
3931 assert_eq!(
3932 second
3933 .bitmap
3934 .to_positions()
3935 .expect("test operation should succeed"),
3936 vec![1, 2]
3937 );
3938 }
3939
3940 #[test]
3941 fn pack_bitmap_writer_roundtrips_lookup_table() {
3942 let object_types = [ObjectType::Commit, ObjectType::Commit, ObjectType::Tree];
3943 let mut writer =
3944 PackBitmapWriter::new(ObjectFormat::Sha1, pack_checksum_sha1(), &object_types)
3945 .expect("test operation should succeed")
3946 .with_lookup_table(true);
3947 writer
3948 .add_commit(0, 1, &[2])
3949 .expect("test operation should succeed");
3950 writer
3951 .add_commit(1, 0, &[2])
3952 .expect("test operation should succeed");
3953 let bytes = writer.write().expect("test operation should succeed");
3954 let parsed = PackBitmapIndex::parse(&bytes, ObjectFormat::Sha1, 3)
3955 .expect("test operation should succeed");
3956 assert!(parsed.lookup_table);
3957 assert_ne!(parsed.options & PackBitmapIndex::OPTION_LOOKUP_TABLE, 0);
3958 assert_eq!(parsed.entries.len(), 2);
3959 }
3960
3961 #[test]
3962 fn pack_bitmap_index_recomputes_checksum_on_write() {
3963 let object_types = [ObjectType::Commit, ObjectType::Blob];
3966 let writer = PackBitmapWriter::new(ObjectFormat::Sha1, pack_checksum_sha1(), &object_types)
3967 .expect("test operation should succeed");
3968 let mut index = writer.build().expect("test operation should succeed");
3969 assert_eq!(index.index_checksum.as_bytes(), [0u8; 20]);
3971 index.entries.clear(); index.entries.push(PackBitmapEntry {
3973 object_position: 0,
3974 xor_offset: 0,
3975 flags: 0,
3976 bitmap: EwahBitmap::from_positions(2, &[0, 1]).expect("test operation should succeed"),
3977 });
3978 let bytes = index.write().expect("test operation should succeed");
3979 let parsed = PackBitmapIndex::parse(&bytes, ObjectFormat::Sha1, 2)
3981 .expect("test operation should succeed");
3982 assert_ne!(parsed.index_checksum.as_bytes(), [0u8; 20]);
3983 }
3984
3985 #[test]
3986 fn pack_bitmap_writer_rejects_non_commit_selection() {
3987 let object_types = [ObjectType::Commit, ObjectType::Blob];
3988 let mut writer =
3989 PackBitmapWriter::new(ObjectFormat::Sha1, pack_checksum_sha1(), &object_types)
3990 .expect("test operation should succeed");
3991 assert!(writer.add_commit(1, 1, &[]).is_err());
3993 assert!(writer.add_commit(5, 5, &[]).is_err());
3995 assert!(writer.add_commit(0, 5, &[]).is_err());
3997 assert!(writer.add_commit(0, 0, &[9]).is_err());
3999 }
4000
4001 #[test]
4002 fn pack_bitmap_writer_rejects_checksum_format_mismatch() {
4003 let sha256_checksum = sley_core::digest_bytes(ObjectFormat::Sha256, b"pack")
4004 .expect("test operation should succeed");
4005 assert!(
4006 PackBitmapWriter::new(ObjectFormat::Sha1, sha256_checksum, &[ObjectType::Commit])
4007 .is_err()
4008 );
4009 }
4010
4011 #[test]
4012 fn pack_bitmap_writer_rejects_bad_name_hash_cache_len() {
4013 let writer = PackBitmapWriter::new(
4014 ObjectFormat::Sha1,
4015 pack_checksum_sha1(),
4016 &[ObjectType::Commit],
4017 )
4018 .expect("test operation should succeed");
4019 assert!(writer.with_name_hash_cache(vec![1, 2]).is_err());
4020 }
4021
4022 #[test]
4023 fn pack_bitmap_index_write_rejects_inconsistent_cache_flag() {
4024 let mut index = PackBitmapWriter::new(
4025 ObjectFormat::Sha1,
4026 pack_checksum_sha1(),
4027 &[ObjectType::Commit],
4028 )
4029 .expect("test operation should succeed")
4030 .build()
4031 .expect("test operation should succeed");
4032 index.options |= PackBitmapIndex::OPTION_HASH_CACHE;
4034 assert!(index.write().is_err());
4035 index.options = PackBitmapIndex::OPTION_FULL_DAG;
4037 index.name_hash_cache = Some(vec![0]);
4038 assert!(index.write().is_err());
4039 }
4040
4041 #[test]
4042 fn write_bitmap_roundtrips_through_upstream_git_parser() {
4043 let root = unique_temp_dir("git-pack-bitmap-writer");
4047 fs::create_dir_all(&root).expect("test operation should succeed");
4048 {
4049 run_git_success(&root, &["init", "-q", "-b", "main"]);
4050 run_git_success(
4051 &root,
4052 &[
4053 "-c",
4054 "user.name=Example User",
4055 "-c",
4056 "user.email=example@example.invalid",
4057 "commit",
4058 "--allow-empty",
4059 "-q",
4060 "-m",
4061 "one",
4062 ],
4063 );
4064 run_git_success(&root, &["repack", "-adb"]);
4065 let pack_dir = root.join(".git").join("objects").join("pack");
4066 let idx_path = single_path_with_extension(&pack_dir, "idx");
4067 let index = PackIndex::parse(
4068 &fs::read(idx_path).expect("test operation should succeed"),
4069 ObjectFormat::Sha1,
4070 )
4071 .expect("test operation should succeed");
4072 let pack_path = single_path_with_extension(&pack_dir, "pack");
4074 let pack =
4075 PackFile::parse_sha1(&fs::read(pack_path).expect("test operation should succeed"))
4076 .expect("test operation should succeed");
4077 let mut offsets: Vec<u64> = index.entries.iter().map(|entry| entry.offset).collect();
4080 offsets.sort_unstable();
4081 let position_of = |offset: u64| -> u32 {
4082 offsets
4083 .iter()
4084 .position(|value| *value == offset)
4085 .expect("test operation should succeed") as u32
4086 };
4087 let mut object_types = vec![ObjectType::Blob; index.entries.len()];
4088 for entry in &index.entries {
4089 let position = position_of(entry.offset) as usize;
4090 if let Some(parsed) = pack
4092 .entries
4093 .iter()
4094 .find(|po| po.entry.offset == entry.offset)
4095 {
4096 object_types[position] = parsed.object.object_type;
4097 }
4098 }
4099 let commit_position = object_types
4101 .iter()
4102 .position(|ty| *ty == ObjectType::Commit)
4103 .expect("test operation should succeed") as u32;
4104 let commit_index_position = index
4106 .entries
4107 .iter()
4108 .position(|entry| position_of(entry.offset) == commit_position)
4109 .expect("test operation should succeed")
4110 as u32;
4111 let reachable: Vec<u32> = (0..index.entries.len() as u32).collect();
4112 let bytes = write_bitmap(
4113 ObjectFormat::Sha1,
4114 index.pack_checksum.clone(),
4115 &object_types,
4116 &[(commit_position, commit_index_position, reachable)],
4117 None,
4118 )
4119 .expect("test operation should succeed");
4120 let parsed = PackBitmapIndex::parse(&bytes, ObjectFormat::Sha1, index.entries.len())
4121 .expect("test operation should succeed");
4122 assert_eq!(parsed.pack_checksum, index.pack_checksum);
4123 assert_eq!(parsed.entries.len(), 1);
4124 assert_eq!(
4125 parsed.entries[0]
4126 .bitmap
4127 .to_positions()
4128 .expect("test operation should succeed")
4129 .len(),
4130 index.entries.len()
4131 );
4132 };
4133 let _ = fs::remove_dir_all(&root);
4134 }
4135
4136 fn zlib_compress(bytes: &[u8]) -> Vec<u8> {
4139 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
4140 encoder
4141 .write_all(bytes)
4142 .expect("test operation should succeed");
4143 encoder.finish().expect("test operation should succeed")
4144 }
4145
4146 fn pack_with_declared_object_count(declared: u32) -> Vec<u8> {
4149 let mut pack = Vec::new();
4150 pack.extend_from_slice(b"PACK");
4151 pack.extend_from_slice(&2u32.to_be_bytes());
4152 pack.extend_from_slice(&declared.to_be_bytes());
4153 let checksum = sley_core::digest_bytes(ObjectFormat::Sha1, &pack)
4154 .expect("test operation should succeed");
4155 pack.extend_from_slice(checksum.as_bytes());
4156 pack
4157 }
4158
4159 #[test]
4167 fn rejects_absurd_declared_object_count_without_preallocating() {
4168 const CHILD_ENV: &str = "SLEY_PACK_OBJECT_COUNT_BOMB_CHILD";
4169 const TEST_PATH: &str = "tests::rejects_absurd_declared_object_count_without_preallocating";
4170
4171 if std::env::var_os(CHILD_ENV).is_some() {
4172 for declared in [u32::MAX, 1 << 30, 1 << 24] {
4173 let pack = pack_with_declared_object_count(declared);
4174 assert!(
4175 PackFile::parse_sha1(&pack).is_err(),
4176 "a 32-byte pack declaring {declared} objects must be rejected"
4177 );
4178 assert!(
4179 PackIndex::write_v2_for_pack(&pack, ObjectFormat::Sha1).is_err(),
4180 "index-pack must reject {declared} objects in a 32-byte pack too"
4181 );
4182 assert!(
4183 PackFile::verify_pack_stats(&pack, ObjectFormat::Sha1).is_err(),
4184 "verify-pack must reject {declared} objects in a 32-byte pack too"
4185 );
4186 }
4187 return;
4188 }
4189
4190 let exe = std::env::current_exe().expect("test binary path should be available");
4191 let status = Command::new(exe)
4192 .args(["--exact", TEST_PATH, "--nocapture"])
4193 .env(CHILD_ENV, "1")
4194 .status()
4195 .expect("re-running the test binary should succeed");
4196 assert!(
4197 status.success(),
4198 "child process died ({status:?}) parsing packs with oversized declared \
4199 object counts — the declared count reached an allocation instead of a \
4200 bounds check"
4201 );
4202 }
4203
4204 #[test]
4208 fn rejects_declared_object_count_larger_than_pack_can_hold() {
4209 let pack = pack_with_declared_object_count(64);
4210 let error = PackFile::parse_sha1(&pack).expect_err("declared count must be rejected");
4211 assert!(
4212 format!("{error}").contains("only has room for"),
4213 "expected a declared-count bound error, got: {error}"
4214 );
4215 }
4216
4217 fn literal_delta(base_len: usize, result: &[u8]) -> Vec<u8> {
4221 assert!(result.len() <= 0x7f, "insert instruction size is one byte");
4222 let mut delta = Vec::new();
4223 write_delta_varint(&mut delta, base_len as u64);
4224 write_delta_varint(&mut delta, result.len() as u64);
4225 delta.push(result.len() as u8);
4226 delta.extend_from_slice(result);
4227 delta
4228 }
4229
4230 fn chain_bodies(depth: usize) -> Vec<Vec<u8>> {
4232 (0..=depth)
4233 .map(|idx| format!("{idx:08}").into_bytes())
4234 .collect()
4235 }
4236
4237 fn ofs_delta_chain_pack(format: ObjectFormat, depth: usize) -> Vec<u8> {
4240 let bodies = chain_bodies(depth);
4241 let mut pack = Vec::new();
4242 pack.extend_from_slice(b"PACK");
4243 pack.extend_from_slice(&2u32.to_be_bytes());
4244 pack.extend_from_slice(&(depth as u32 + 1).to_be_bytes());
4245
4246 let mut base_offset = pack.len();
4247 write_entry_header(&mut pack, ObjectType::Blob, bodies[0].len() as u64);
4248 pack.extend_from_slice(&zlib_compress(&bodies[0]));
4249
4250 for idx in 1..=depth {
4251 let delta = literal_delta(bodies[idx - 1].len(), &bodies[idx]);
4252 let entry_offset = pack.len();
4253 write_pack_entry_header_kind(&mut pack, 6, delta.len() as u64);
4254 write_ofs_delta_offset(&mut pack, entry_offset - base_offset);
4255 pack.extend_from_slice(&zlib_compress(&delta));
4256 base_offset = entry_offset;
4257 }
4258
4259 let checksum =
4260 sley_core::digest_bytes(format, &pack).expect("test operation should succeed");
4261 pack.extend_from_slice(checksum.as_bytes());
4262 pack
4263 }
4264
4265 fn ref_delta_chain_pack(format: ObjectFormat, depth: usize, reversed: bool) -> Vec<u8> {
4271 let bodies = chain_bodies(depth);
4272
4273 let mut base_entry = Vec::new();
4274 write_entry_header(&mut base_entry, ObjectType::Blob, bodies[0].len() as u64);
4275 base_entry.extend_from_slice(&zlib_compress(&bodies[0]));
4276
4277 let mut delta_entries = Vec::with_capacity(depth);
4278 for idx in 1..=depth {
4279 let delta = literal_delta(bodies[idx - 1].len(), &bodies[idx]);
4280 let base_oid = sley_core::object_id_for_bytes(format, "blob", &bodies[idx - 1])
4281 .expect("test operation should succeed");
4282 let mut entry = Vec::new();
4283 write_pack_entry_header_kind(&mut entry, 7, delta.len() as u64);
4284 entry.extend_from_slice(base_oid.as_bytes());
4285 entry.extend_from_slice(&zlib_compress(&delta));
4286 delta_entries.push(entry);
4287 }
4288 if reversed {
4289 delta_entries.reverse();
4290 }
4291
4292 let mut pack = Vec::new();
4293 pack.extend_from_slice(b"PACK");
4294 pack.extend_from_slice(&2u32.to_be_bytes());
4295 pack.extend_from_slice(&(depth as u32 + 1).to_be_bytes());
4296 if !reversed {
4297 pack.extend_from_slice(&base_entry);
4298 }
4299 for entry in &delta_entries {
4300 pack.extend_from_slice(entry);
4301 }
4302 if reversed {
4303 pack.extend_from_slice(&base_entry);
4304 }
4305
4306 let checksum =
4307 sley_core::digest_bytes(format, &pack).expect("test operation should succeed");
4308 pack.extend_from_slice(checksum.as_bytes());
4309 pack
4310 }
4311
4312 #[test]
4313 fn header_read_bounds_ofs_delta_recursion_at_the_shared_ceiling() {
4314 let at_ceiling = ofs_delta_chain_pack(ObjectFormat::Sha1, MAX_READ_DELTA_CHAIN_DEPTH);
4315 let at_ceiling_offset = pack_entry_descriptors(&at_ceiling, ObjectFormat::Sha1)
4316 .last()
4317 .expect("the pack has entries")
4318 .offset;
4319 let header = read_object_header_at(
4320 &at_ceiling,
4321 at_ceiling_offset,
4322 ObjectFormat::Sha1,
4323 0,
4324 |_, _| Ok(None),
4325 )
4326 .expect("a header chain at the ceiling must resolve");
4327 assert_eq!(header.type_and_size(), (ObjectType::Blob, 8));
4328
4329 let over_ceiling = ofs_delta_chain_pack(ObjectFormat::Sha1, 5_000);
4330 let over_ceiling_offset = pack_entry_descriptors(&over_ceiling, ObjectFormat::Sha1)
4331 .last()
4332 .expect("the pack has entries")
4333 .offset;
4334 let error = std::thread::Builder::new()
4335 .stack_size(512 * 1024)
4336 .spawn(move || {
4337 read_object_header_at(
4338 &over_ceiling,
4339 over_ceiling_offset,
4340 ObjectFormat::Sha1,
4341 0,
4342 |_, _| Ok(None),
4343 )
4344 .expect_err("a header chain past the ceiling must be rejected")
4345 })
4346 .expect("spawn small-stack thread")
4347 .join()
4348 .expect("the bounded header walk must not overflow its stack");
4349 let rejected_depth = MAX_READ_DELTA_CHAIN_DEPTH + 1;
4350 assert!(
4351 error
4352 .to_string()
4353 .contains(&format!("observed depth {rejected_depth}")),
4354 "expected an actionable depth error, got: {error}"
4355 );
4356 }
4357
4358 #[test]
4359 fn header_read_combines_prior_and_local_delta_depth() {
4360 let pack = ofs_delta_chain_pack(ObjectFormat::Sha1, 2);
4361 let offset = pack_entry_descriptors(&pack, ObjectFormat::Sha1)
4362 .last()
4363 .expect("the pack has entries")
4364 .offset;
4365 let error = read_object_header_at(
4366 &pack,
4367 offset,
4368 ObjectFormat::Sha1,
4369 MAX_READ_DELTA_CHAIN_DEPTH - 1,
4370 |_, _| Ok(None),
4371 )
4372 .expect_err("local ofs-deltas must count prior cross-pack delta links");
4373 let rejected_depth = MAX_READ_DELTA_CHAIN_DEPTH + 1;
4374 assert!(
4375 error
4376 .to_string()
4377 .contains(&format!("observed depth {rejected_depth}")),
4378 "expected the combined depth in the error, got: {error}"
4379 );
4380 }
4381
4382 #[test]
4383 fn header_cache_preserves_the_cumulative_depth_limit() {
4384 let pack = ofs_delta_chain_pack(ObjectFormat::Sha1, 3);
4385 let descriptors = pack_entry_descriptors(&pack, ObjectFormat::Sha1);
4386 let target_offset = descriptors[3].offset;
4387 let cached_base_offset = descriptors[2].offset;
4388 let initial_depth = MAX_READ_DELTA_CHAIN_DEPTH - 2;
4389 let rejected_depth = MAX_READ_DELTA_CHAIN_DEPTH + 1;
4390
4391 let mut direct_cache = MapHeaderTypeCache::default();
4392 read_object_header_at_with_cache(
4393 &pack,
4394 target_offset,
4395 ObjectFormat::Sha1,
4396 0,
4397 |_, _| Ok(None),
4398 &mut direct_cache,
4399 )
4400 .expect("the top-level read should warm the target header");
4401 let direct_error = read_object_header_at_with_cache(
4402 &pack,
4403 target_offset,
4404 ObjectFormat::Sha1,
4405 initial_depth,
4406 |_, _| Ok(None),
4407 &mut direct_cache,
4408 )
4409 .expect_err("a direct cache hit must include its represented chain depth");
4410 assert!(
4411 direct_error
4412 .to_string()
4413 .contains(&format!("observed depth {rejected_depth}")),
4414 "expected the direct cached depth in the error, got: {direct_error}"
4415 );
4416
4417 let mut base_cache = MapHeaderTypeCache::default();
4418 read_object_header_at_with_cache(
4419 &pack,
4420 cached_base_offset,
4421 ObjectFormat::Sha1,
4422 0,
4423 |_, _| Ok(None),
4424 &mut base_cache,
4425 )
4426 .expect("the top-level read should warm the target's immediate base");
4427 let base_error = read_object_header_at_with_cache(
4428 &pack,
4429 target_offset,
4430 ObjectFormat::Sha1,
4431 initial_depth,
4432 |_, _| Ok(None),
4433 &mut base_cache,
4434 )
4435 .expect_err("an internal base-cache hit must include its represented chain depth");
4436 assert!(
4437 base_error
4438 .to_string()
4439 .contains(&format!("observed depth {rejected_depth}")),
4440 "expected the cached base depth in the error, got: {base_error}"
4441 );
4442 }
4443
4444 #[test]
4447 fn resolves_delta_chain_at_the_depth_ceiling() {
4448 for pack in [
4449 ofs_delta_chain_pack(ObjectFormat::Sha1, DEFAULT_PACK_DEPTH),
4450 ref_delta_chain_pack(ObjectFormat::Sha1, DEFAULT_PACK_DEPTH, false),
4451 ] {
4452 let parsed = PackFile::parse_sha1(&pack).expect("a chain at the ceiling must resolve");
4453 assert_eq!(parsed.entries.len(), DEFAULT_PACK_DEPTH + 1);
4454 }
4455 }
4456
4457 #[test]
4458 fn configured_delta_chain_depth_limit_allows_deeper_pack() {
4459 let limits = PackReadLimits {
4460 max_delta_depth: 60,
4461 ..PackReadLimits::default()
4462 };
4463 let pack = ofs_delta_chain_pack(ObjectFormat::Sha1, 60);
4464
4465 let parsed = PackFile::parse_with_limits(&pack, ObjectFormat::Sha1, limits)
4466 .expect("a chain at the configured ceiling must resolve");
4467 assert_eq!(parsed.entries.len(), 61);
4468
4469 let indexed = PackIndex::write_v2_for_pack_with_limits(&pack, ObjectFormat::Sha1, limits)
4470 .expect("the in-memory indexer must use the configured ceiling");
4471 assert_eq!(indexed.entries.len(), 61);
4472 }
4473
4474 #[test]
4475 fn configured_delta_chain_depth_limit_remains_finite_and_actionable() {
4476 let limits = PackReadLimits {
4477 max_delta_depth: 60,
4478 ..PackReadLimits::default()
4479 };
4480 let pack = ofs_delta_chain_pack(ObjectFormat::Sha1, 61);
4481
4482 let error = PackFile::parse_with_limits(&pack, ObjectFormat::Sha1, limits)
4483 .expect_err("a chain past the configured ceiling must be rejected");
4484 let message = format!("{error}");
4485
4486 assert!(
4487 message.contains("observed depth 61"),
4488 "expected the observed depth in the error, got: {error}"
4489 );
4490 assert!(
4491 message.contains("configured limit 60"),
4492 "expected the configured limit in the error, got: {error}"
4493 );
4494 assert!(
4495 message.contains("PackReadLimits::max_delta_depth")
4496 && message.contains("git repack --depth="),
4497 "expected actionable remedies in the error, got: {error}"
4498 );
4499 }
4500
4501 #[test]
4504 fn rejects_delta_chain_deeper_than_the_ceiling() {
4505 for pack in [
4506 ofs_delta_chain_pack(ObjectFormat::Sha1, DEFAULT_PACK_DEPTH + 1),
4507 ref_delta_chain_pack(ObjectFormat::Sha1, DEFAULT_PACK_DEPTH + 1, false),
4508 ] {
4509 let error =
4510 PackFile::parse_sha1(&pack).expect_err("a chain past the ceiling must be rejected");
4511 assert!(
4512 format!("{error}").contains("exceeds maximum depth"),
4513 "expected a chain-depth error, got: {error}"
4514 );
4515 }
4516 }
4517
4518 #[test]
4523 fn rejects_adversarially_ordered_long_delta_chain_promptly() {
4524 let pack = ref_delta_chain_pack(ObjectFormat::Sha1, 5_000, true);
4525 let started = std::time::Instant::now();
4526 let error = PackFile::parse_sha1(&pack)
4527 .expect_err("a long adversarial chain must be rejected, not resolved");
4528 let elapsed = started.elapsed();
4529 assert!(
4530 format!("{error}").contains("exceeds maximum depth"),
4531 "expected a chain-depth error, got: {error}"
4532 );
4533 assert!(
4534 elapsed < std::time::Duration::from_secs(20),
4535 "rejection took {elapsed:?}; resolution is still doing work proportional \
4536 to the chain length"
4537 );
4538 }
4539
4540 #[test]
4543 fn index_pack_rejects_delta_chain_deeper_than_the_ceiling() {
4544 let pack = ofs_delta_chain_pack(ObjectFormat::Sha1, DEFAULT_PACK_DEPTH + 1);
4545 let error = PackIndex::write_v2_for_pack(&pack, ObjectFormat::Sha1)
4546 .expect_err("index-pack must reject a chain past the ceiling");
4547 assert!(
4548 format!("{error}").contains("exceeds maximum depth"),
4549 "expected a chain-depth error, got: {error}"
4550 );
4551 }
4552}