1#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used))]
4
5use flate2::{Compress, Compression, FlushCompress, Status};
6use sley_core::{GitError, ObjectFormat, ObjectId, Result, StreamingDigest};
7use sley_formats::Bundle;
8use sley_object::{EncodedObject, ObjectType};
9use std::borrow::Borrow;
10use std::cell::RefCell;
11use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
12use std::fmt;
13use std::fs::File;
14use std::io::{Read, Seek, SeekFrom, Write};
15use std::ops::Range;
16use std::path::Path;
17use std::sync::Arc;
18
19pub mod inflate;
26mod delta;
27mod index;
28mod read;
29mod stream;
30mod write;
31
32pub(crate) use delta::*;
33pub use index::*;
34pub use read::*;
35pub(crate) use stream::*;
36pub use write::*;
37
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct PackEntry {
40 pub oid: ObjectId,
41 pub compressed_size: u64,
42 pub uncompressed_size: u64,
43 pub offset: u64,
44}
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct RepackPolicy {
47 pub write_bitmaps: bool,
48 pub cruft_packs: bool,
49 pub geometric_factor: Option<u8>,
50}
51
52#[derive(Debug, Clone, PartialEq, Eq)]
53pub struct PackFile {
54 pub version: u32,
55 pub entries: Vec<PackObject>,
56 pub checksum: ObjectId,
57}
58
59#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct PackObject {
61 pub entry: PackEntry,
62 pub object: EncodedObject,
63}
64
65#[derive(Debug, Clone, PartialEq, Eq)]
68pub struct PackVerifyStat {
69 pub oid: ObjectId,
71 pub object_type: ObjectType,
73 pub size: u64,
75 pub size_in_pack: u64,
78 pub offset: u64,
80 pub delta_depth: u32,
82 pub base_oid: Option<ObjectId>,
85}
86
87#[derive(Debug, Clone, PartialEq, Eq)]
90pub struct PackVerifyStats {
91 pub objects: Vec<PackVerifyStat>,
92 pub checksum: ObjectId,
93}
94
95#[derive(Debug, Clone, PartialEq, Eq)]
96pub struct PackWrite {
97 pub pack: Vec<u8>,
98 pub index: Vec<u8>,
99 pub checksum: ObjectId,
100 pub entries: Vec<PackIndexEntry>,
101 pub delta_count: u32,
102}
103
104#[derive(Debug, Clone, PartialEq, Eq)]
105pub struct PackWriteSummary {
106 pub index: Vec<u8>,
107 pub checksum: ObjectId,
108 pub entries: Vec<PackIndexEntry>,
109 pub delta_count: u32,
110 pub pack_size: u64,
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub struct PackInput<'a> {
115 pub oid: &'a ObjectId,
116 pub object: &'a EncodedObject,
117}
118#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119enum PackObjectKind {
120 Commit,
121 Tree,
122 Blob,
123 Tag,
124 OfsDelta,
125 RefDelta,
126}
127
128#[derive(Debug, Clone, PartialEq, Eq)]
129enum ParsedPackEntry {
130 Resolved(PackObject),
131 Delta {
132 base: DeltaBase,
133 compressed_size: u64,
134 delta_size: u64,
135 offset: u64,
136 delta: Vec<u8>,
137 },
138}
139
140#[derive(Debug, Clone, PartialEq, Eq)]
141enum DeltaBase {
142 Offset(u64),
143 Ref(ObjectId),
144}
145
146struct OnDiskEntry {
150 offset: u64,
151 base: Option<DeltaBase>,
152 stream_size: u64,
153}
154#[derive(Debug, Clone, Copy, PartialEq, Eq)]
155struct EntryHeader {
156 kind: PackObjectKind,
157 size: u64,
158}
159fn next_byte(bytes: &[u8], offset: &mut usize) -> Result<u8> {
160 let Some(byte) = bytes.get(*offset).copied() else {
161 return Err(GitError::InvalidFormat(
162 "truncated pack entry header".into(),
163 ));
164 };
165 *offset += 1;
166 Ok(byte)
167}
168
169fn u16_be(bytes: &[u8]) -> u16 {
170 u16::from_be_bytes([bytes[0], bytes[1]])
171}
172
173fn u32_be(bytes: &[u8]) -> u32 {
174 u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
175}
176
177fn u64_be(bytes: &[u8]) -> u64 {
178 u64::from_be_bytes([
179 bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
180 ])
181}
182fn checked_range(
183 start: usize,
184 count: usize,
185 width: usize,
186 total: usize,
187) -> Result<std::ops::Range<usize>> {
188 let len = count
189 .checked_mul(width)
190 .ok_or_else(|| GitError::InvalidFormat("pack index table overflow".into()))?;
191 let end = start
192 .checked_add(len)
193 .ok_or_else(|| GitError::InvalidFormat("pack index table overflow".into()))?;
194 if end > total {
195 return Err(GitError::InvalidFormat("truncated pack index table".into()));
196 }
197 Ok(start..end)
198}
199
200fn validate_position_permutation(positions: &[u32]) -> Result<()> {
201 let mut seen = vec![false; positions.len()];
202 for position in positions {
203 let idx = *position as usize;
204 if idx >= positions.len() {
205 return Err(GitError::InvalidFormat(
206 "reverse index position points past object table".into(),
207 ));
208 }
209 if seen[idx] {
210 return Err(GitError::InvalidFormat(
211 "reverse index position is duplicated".into(),
212 ));
213 }
214 seen[idx] = true;
215 }
216 Ok(())
217}
218
219thread_local! {
225 pub(crate) static INFLATE: RefCell<flate2::Decompress> = RefCell::new(flate2::Decompress::new(true));
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231 use flate2::Compression;
232 use flate2::read::ZlibDecoder;
233 use flate2::write::ZlibEncoder;
234 use std::fs;
235 use std::io::Read;
236 use std::io::Write;
237 use std::path::{Path, PathBuf};
238 use std::process::Command;
239 use std::time::{SystemTime, UNIX_EPOCH};
240
241 fn delta_pack_options(prefer_ofs_delta: bool) -> PackWriteOptions {
242 PackWriteOptions::new()
243 .with_prefer_ofs_delta(prefer_ofs_delta)
244 .with_reorder(false)
245 }
246
247 #[test]
248 fn parses_single_blob_pack() {
249 let pack = single_object_pack(ObjectFormat::Sha1, ObjectType::Blob, b"hello\n");
250 let parsed = PackFile::parse_sha1(&pack).expect("test operation should succeed");
251 assert_eq!(parsed.version, 2);
252 assert_eq!(parsed.entries.len(), 1);
253 let object = &parsed.entries[0].object;
254 assert_eq!(object.object_type, ObjectType::Blob);
255 assert_eq!(object.body, b"hello\n");
256 assert_eq!(
257 parsed.entries[0].entry.oid.to_hex(),
258 "ce013625030ba8dba906f756967f9e9ca394464a"
259 );
260 }
261
262 #[test]
263 fn parses_single_blob_pack_sha256() {
264 let pack = single_object_pack(ObjectFormat::Sha256, ObjectType::Blob, b"hello\n");
265 let parsed =
266 PackFile::parse(&pack, ObjectFormat::Sha256).expect("test operation should succeed");
267 assert_eq!(parsed.version, 2);
268 assert_eq!(parsed.entries.len(), 1);
269 let object = &parsed.entries[0].object;
270 assert_eq!(object.object_type, ObjectType::Blob);
271 assert_eq!(object.body, b"hello\n");
272 assert_eq!(
273 parsed.entries[0].entry.oid,
274 object
275 .object_id(ObjectFormat::Sha256)
276 .expect("test operation should succeed")
277 );
278 }
279
280 #[test]
281 fn parses_bundle_pack_payload_with_bundle_format() {
282 let pack = single_object_pack(ObjectFormat::Sha1, ObjectType::Blob, b"bundle\n");
283 let oid = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"bundle\n")
284 .expect("test operation should succeed");
285 let bundle_bytes = format!("# v2 git bundle\n{oid} refs/heads/main\n\n")
286 .into_bytes()
287 .into_iter()
288 .chain(pack)
289 .collect::<Vec<_>>();
290 let bundle = Bundle::parse(&bundle_bytes, ObjectFormat::Sha1)
291 .expect("test operation should succeed");
292
293 let parsed = PackFile::parse_bundle(&bundle).expect("test operation should succeed");
294 assert_eq!(parsed.entries.len(), 1);
295 assert_eq!(parsed.entries[0].object.object_type, ObjectType::Blob);
296 assert_eq!(parsed.entries[0].object.body, b"bundle\n");
297 }
298
299 fn lying_size_blob_pack(format: ObjectFormat, declared_size: u64, real_body: &[u8]) -> Vec<u8> {
305 let mut pack = Vec::new();
306 pack.extend_from_slice(b"PACK");
307 pack.extend_from_slice(&2u32.to_be_bytes());
308 pack.extend_from_slice(&1u32.to_be_bytes());
309 write_pack_entry_header_kind(&mut pack, 3, declared_size);
311 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
312 encoder
313 .write_all(real_body)
314 .expect("test operation should succeed");
315 pack.extend_from_slice(&encoder.finish().expect("test operation should succeed"));
316 let checksum =
317 sley_core::digest_bytes(format, &pack).expect("test operation should succeed");
318 pack.extend_from_slice(checksum.as_bytes());
319 pack
320 }
321
322 #[test]
335 fn rejects_decompression_bomb_header_without_oom() {
336 for &declared in &[u64::MAX, 100 * 1024 * 1024 * 1024, u64::from(u32::MAX) * 4] {
337 let pack = lying_size_blob_pack(ObjectFormat::Sha1, declared, b"tiny\n");
338 let handle = std::thread::spawn(move || PackFile::parse_sha1(&pack));
339 let result = handle.join();
340 assert!(
342 result.is_ok(),
343 "parsing a bomb header (declared={declared}) panicked instead of erroring cleanly"
344 );
345 let parse_result = result.expect("parse thread should not panic on a bomb header");
347 assert!(
348 parse_result.is_err(),
349 "bomb header (declared={declared}) should be rejected as invalid"
350 );
351 }
352 }
353
354 fn lying_result_size_delta_pack(
361 format: ObjectFormat,
362 declared_result_size: u64,
363 delta_kind: DeltaKind,
364 ) -> Vec<u8> {
365 let base = b"hello";
366 let result = b"hello world"; let mut delta = Vec::new();
370 write_delta_varint(&mut delta, base.len() as u64);
371 write_delta_varint(&mut delta, declared_result_size);
372 let suffix = &result[base.len()..];
374 delta.push(0x90); delta.push(base.len() as u8);
376 delta.push(suffix.len() as u8);
377 delta.extend_from_slice(suffix);
378
379 let mut pack = Vec::new();
380 pack.extend_from_slice(b"PACK");
381 pack.extend_from_slice(&2u32.to_be_bytes());
382 pack.extend_from_slice(&2u32.to_be_bytes());
383
384 let base_offset = pack.len();
385 write_entry_header(&mut pack, ObjectType::Blob, base.len() as u64);
386 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
387 encoder
388 .write_all(base)
389 .expect("test operation should succeed");
390 pack.extend_from_slice(&encoder.finish().expect("test operation should succeed"));
391
392 let delta_offset = pack.len();
393 write_pack_entry_header_kind(
394 &mut pack,
395 match delta_kind {
396 DeltaKind::Offset => 6,
397 DeltaKind::Ref => 7,
398 },
399 delta.len() as u64,
400 );
401 match delta_kind {
402 DeltaKind::Offset => write_ofs_delta_offset(&mut pack, delta_offset - base_offset),
403 DeltaKind::Ref => {
404 let base_oid = sley_core::object_id_for_bytes(format, "blob", base)
405 .expect("test operation should succeed");
406 pack.extend_from_slice(base_oid.as_bytes());
407 }
408 }
409 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
410 encoder
411 .write_all(&delta)
412 .expect("test operation should succeed");
413 pack.extend_from_slice(&encoder.finish().expect("test operation should succeed"));
414
415 let checksum =
416 sley_core::digest_bytes(format, &pack).expect("test operation should succeed");
417 pack.extend_from_slice(checksum.as_bytes());
418 pack
419 }
420
421 #[test]
431 fn rejects_delta_result_size_bomb_without_oom() {
432 let bombs: &[u64] = &[u64::MAX, 1024 * 1024 * 1024 * 1024];
433 for &declared in bombs {
434 for delta_kind in [DeltaKind::Ref, DeltaKind::Offset] {
435 let pack = lying_result_size_delta_pack(ObjectFormat::Sha1, declared, delta_kind);
436 let handle = std::thread::spawn(move || PackFile::parse_sha1(&pack));
437 let join_result = handle.join();
438 assert!(
439 join_result.is_ok(),
440 "delta bomb (declared={declared}, kind={delta_kind:?}) panicked/aborted \
441 instead of erroring cleanly"
442 );
443 let parse_result =
444 join_result.expect("parse thread should not panic on a delta bomb");
445 assert!(
446 parse_result.is_err(),
447 "delta bomb (declared={declared}, kind={delta_kind:?}) should be rejected \
448 as invalid (result.len() != declared)"
449 );
450 }
451 }
452 }
453
454 #[test]
458 fn applies_legitimate_delta_after_result_size_bound() {
459 for delta_kind in [DeltaKind::Ref, DeltaKind::Offset] {
460 let base = b"hello";
461 let result = b"hello world";
462 let pack = two_object_delta_pack(ObjectFormat::Sha1, base, result, delta_kind);
463 let parsed = PackFile::parse_sha1(&pack).expect("legitimate delta should resolve");
464 assert_eq!(parsed.entries.len(), 2);
465 assert_eq!(parsed.entries[0].object.body, base);
466 assert_eq!(parsed.entries[1].object.body, result);
467 }
468 }
469
470 #[test]
471 fn rejects_bundle_pack_payload_with_wrong_object_format() {
472 let pack = single_object_pack(ObjectFormat::Sha1, ObjectType::Blob, b"bundle\n");
473 let oid = sley_core::object_id_for_bytes(ObjectFormat::Sha256, "blob", b"bundle\n")
474 .expect("test operation should succeed");
475 let bundle_bytes =
476 format!("# v3 git bundle\n@object-format=sha256\n{oid} refs/heads/main\n\n")
477 .into_bytes()
478 .into_iter()
479 .chain(pack)
480 .collect::<Vec<_>>();
481 let bundle = Bundle::parse(&bundle_bytes, ObjectFormat::Sha1)
482 .expect("test operation should succeed");
483
484 assert!(PackFile::parse_bundle(&bundle).is_err());
485 }
486
487 fn assert_pack_index_view_matches_owned(index: &[u8], format: ObjectFormat) {
488 let owned = PackIndex::parse(index, format).expect("test operation should succeed");
489 let view = PackIndexView::parse(index, format).expect("test operation should succeed");
490 let owned_view =
491 PackIndexViewData::parse(Arc::from(index.to_vec().into_boxed_slice()), format)
492 .expect("test operation should succeed");
493
494 assert_eq!(view.version, owned.version);
495 assert_eq!(view.count, owned.entries.len());
496 assert_eq!(view.count(), owned.entries.len());
497 assert_eq!(view.fanout(), &owned.fanout);
498 assert_eq!(view.pack_checksum, owned.pack_checksum);
499 assert_eq!(view.index_checksum, owned.index_checksum);
500 assert_eq!(owned_view.version, owned.version);
501 assert_eq!(owned_view.count(), owned.entries.len());
502 assert_eq!(owned_view.fanout(), &owned.fanout);
503 assert_eq!(owned_view.pack_checksum, owned.pack_checksum);
504 assert_eq!(owned_view.index_checksum, owned.index_checksum);
505 for entry in &owned.entries {
506 let owned_found = owned
507 .find(&entry.oid)
508 .expect("test operation should succeed");
509 let expected = Some(PackIndexLookup {
510 crc32: owned_found.crc32,
511 offset: owned_found.offset,
512 });
513 assert_eq!(view.find(&entry.oid), expected);
514 assert_eq!(owned_view.find(&entry.oid), expected);
515 }
516 }
517
518 #[test]
519 fn writes_pack_and_index_that_round_trip() {
520 let object = EncodedObject::new(ObjectType::Blob, b"hello\n".to_vec());
521 let written = PackFile::write_undeltified_sha1(std::slice::from_ref(&object))
522 .expect("test operation should succeed");
523 let pack = PackFile::parse_sha1(&written.pack).expect("test operation should succeed");
524 let index =
525 PackIndex::parse_v2_sha1(&written.index).expect("test operation should succeed");
526 let oid = object
527 .object_id(ObjectFormat::Sha1)
528 .expect("test operation should succeed");
529 assert_eq!(pack.entries[0].object, object);
530 assert_eq!(index.pack_checksum, pack.checksum);
531 assert_eq!(
532 index
533 .find(&oid)
534 .expect("test operation should succeed")
535 .offset,
536 12
537 );
538 }
539
540 #[test]
541 fn pack_index_view_matches_owned_index_for_generated_sha1_pack() {
542 let objects = (0..8)
543 .map(|idx| {
544 EncodedObject::new(
545 ObjectType::Blob,
546 format!("borrowed pack index view sha1 object {idx}\n").into_bytes(),
547 )
548 })
549 .collect::<Vec<_>>();
550 let written = PackFile::write_packed(&objects, ObjectFormat::Sha1)
551 .expect("test operation should succeed");
552
553 assert_pack_index_view_matches_owned(&written.index, ObjectFormat::Sha1);
554
555 let view =
556 PackIndexView::parse_v2_sha1(&written.index).expect("test operation should succeed");
557 let missing = sley_core::object_id_for_bytes(
558 ObjectFormat::Sha1,
559 "blob",
560 b"not present in borrowed index\n",
561 )
562 .expect("test operation should succeed");
563 assert_eq!(view.find(&missing), None);
564 }
565
566 #[test]
567 fn writes_sha256_pack_and_index_that_round_trip() {
568 let object = EncodedObject::new(ObjectType::Blob, b"hello sha256\n".to_vec());
569 let written =
570 PackFile::write_undeltified(std::slice::from_ref(&object), ObjectFormat::Sha256)
571 .expect("test operation should succeed");
572 let pack = PackFile::parse(&written.pack, ObjectFormat::Sha256)
573 .expect("test operation should succeed");
574 let index = PackIndex::parse(&written.index, ObjectFormat::Sha256)
575 .expect("test operation should succeed");
576 let oid = object
577 .object_id(ObjectFormat::Sha256)
578 .expect("test operation should succeed");
579 assert_eq!(pack.entries[0].object, object);
580 assert_eq!(index.pack_checksum, pack.checksum);
581 assert_eq!(index.pack_checksum.format(), ObjectFormat::Sha256);
582 assert_eq!(index.index_checksum.format(), ObjectFormat::Sha256);
583 assert_eq!(
584 index
585 .find(&oid)
586 .expect("test operation should succeed")
587 .offset,
588 12
589 );
590 }
591
592 #[test]
593 fn pack_index_view_matches_owned_index_for_generated_sha256_pack() {
594 let objects = (0..4)
595 .map(|idx| {
596 EncodedObject::new(
597 ObjectType::Blob,
598 format!("borrowed pack index view sha256 object {idx}\n").into_bytes(),
599 )
600 })
601 .collect::<Vec<_>>();
602 let written = PackFile::write_undeltified(&objects, ObjectFormat::Sha256)
603 .expect("test operation should succeed");
604
605 assert_pack_index_view_matches_owned(&written.index, ObjectFormat::Sha256);
606 }
607
608 #[test]
609 fn indexes_existing_sha256_pack_bytes() {
610 let object = EncodedObject::new(ObjectType::Blob, b"index raw sha256 pack\n".to_vec());
611 let written =
612 PackFile::write_undeltified(std::slice::from_ref(&object), ObjectFormat::Sha256)
613 .expect("test operation should succeed");
614
615 let indexed = PackIndex::write_v2_for_pack(&written.pack, ObjectFormat::Sha256)
616 .expect("test operation should succeed");
617 let index = PackIndex::parse(&indexed.index, ObjectFormat::Sha256)
618 .expect("test operation should succeed");
619
620 assert_eq!(indexed.pack_checksum, written.checksum);
621 assert_eq!(indexed.entries, written.entries);
622 assert_eq!(index.pack_checksum, written.checksum);
623 assert_eq!(index.entries, written.entries);
624 }
625
626 #[test]
627 fn indexes_existing_delta_pack_bytes() {
628 let (base, changed) = similar_blob_objects();
629 let options = delta_pack_options(true);
630 let written = PackFile::write_packed_with_options(
631 &[base, changed.clone()],
632 ObjectFormat::Sha1,
633 &options,
634 )
635 .expect("test operation should succeed");
636
637 let indexed = PackIndex::write_v2_for_pack_sha1(&written.pack)
638 .expect("test operation should succeed");
639 let index =
640 PackIndex::parse_v2_sha1(&indexed.index).expect("test operation should succeed");
641 let changed_oid = changed
642 .object_id(ObjectFormat::Sha1)
643 .expect("test operation should succeed");
644
645 assert_eq!(indexed.pack_checksum, written.checksum);
646 assert_eq!(indexed.entries, written.entries);
647 assert_eq!(
648 index
649 .find(&changed_oid)
650 .expect("test operation should succeed")
651 .offset,
652 written.entries[1].offset
653 );
654 assert_eq!(
655 index
656 .find(&changed_oid)
657 .expect("test operation should succeed")
658 .crc32,
659 written.entries[1].crc32
660 );
661 }
662
663 #[test]
664 fn writes_ref_delta_pack_and_index_that_round_trip() {
665 let (base, changed) = similar_blob_objects();
666 let options = delta_pack_options(false);
667 let written = PackFile::write_packed_with_options(
668 &[base.clone(), changed.clone()],
669 ObjectFormat::Sha1,
670 &options,
671 )
672 .expect("test operation should succeed");
673 let mut second_offset = written.entries[1].offset as usize;
674 let header = parse_entry_header(&written.pack, &mut second_offset)
675 .expect("test operation should succeed");
676 assert_eq!(header.kind, PackObjectKind::RefDelta);
677
678 let pack = PackFile::parse_sha1(&written.pack).expect("test operation should succeed");
679 let index =
680 PackIndex::parse_v2_sha1(&written.index).expect("test operation should succeed");
681 let oid = changed
682 .object_id(ObjectFormat::Sha1)
683 .expect("test operation should succeed");
684 assert_eq!(pack.entries[0].object, base);
685 assert_eq!(pack.entries[1].object, changed);
686 assert_eq!(index.pack_checksum, pack.checksum);
687 assert_eq!(
688 index
689 .find(&oid)
690 .expect("test operation should succeed")
691 .offset,
692 written.entries[1].offset
693 );
694 }
695
696 #[test]
697 fn read_object_at_matches_full_parse_for_ofs_delta_pack() {
698 let (base, changed) = similar_blob_objects();
699 let options = delta_pack_options(true);
700 let written = PackFile::write_packed_with_options(
701 &[base, changed.clone()],
702 ObjectFormat::Sha1,
703 &options,
704 )
705 .expect("test operation should succeed");
706 let mut second = written.entries[1].offset as usize;
708 assert_eq!(
709 parse_entry_header(&written.pack, &mut second)
710 .expect("test operation should succeed")
711 .kind,
712 PackObjectKind::OfsDelta
713 );
714 let parsed = PackFile::parse_sha1(&written.pack).expect("test operation should succeed");
716 for po in &parsed.entries {
717 let got =
718 read_object_at_arc(&written.pack, po.entry.offset, ObjectFormat::Sha1, |_| {
719 Ok(None)
720 })
721 .expect("test operation should succeed");
722 assert_eq!(*got, po.object, "offset {}", po.entry.offset);
723 }
724 }
725
726 #[derive(Default)]
729 struct MapHeaderTypeCache(HashMap<u64, (ObjectType, u64)>);
730
731 impl HeaderTypeCache for MapHeaderTypeCache {
732 fn get(&self, pack_offset: u64) -> Option<(ObjectType, u64)> {
733 self.0.get(&pack_offset).copied()
734 }
735 fn put(&mut self, pack_offset: u64, header: (ObjectType, u64)) {
736 self.0.insert(pack_offset, header);
737 }
738 }
739
740 #[test]
741 fn read_object_header_at_cached_matches_uncached_cold_and_warm_for_ofs_delta() {
742 let (base, changed) = similar_blob_objects();
743 let options = delta_pack_options(true);
744 let written =
745 PackFile::write_packed_with_options(&[base, changed], ObjectFormat::Sha1, &options)
746 .expect("test operation should succeed");
747 let mut second = written.entries[1].offset as usize;
749 assert_eq!(
750 parse_entry_header(&written.pack, &mut second)
751 .expect("test operation should succeed")
752 .kind,
753 PackObjectKind::OfsDelta
754 );
755
756 let parsed = PackFile::parse_sha1(&written.pack).expect("test operation should succeed");
757 let mut cache = MapHeaderTypeCache::default();
758 for po in &parsed.entries {
759 let uncached =
760 read_object_header_at(&written.pack, po.entry.offset, ObjectFormat::Sha1, |_| {
761 Ok(None)
762 })
763 .expect("test operation should succeed");
764 assert_eq!(
766 uncached,
767 (po.object.object_type, po.object.body.len() as u64),
768 "uncached header at offset {}",
769 po.entry.offset
770 );
771 let cold = read_object_header_at_with_cache(
773 &written.pack,
774 po.entry.offset,
775 ObjectFormat::Sha1,
776 |_| Ok(None),
777 &mut cache,
778 )
779 .expect("test operation should succeed");
780 assert_eq!(cold, uncached, "cold cache at offset {}", po.entry.offset);
781 }
782 for po in &parsed.entries {
785 let warm = read_object_header_at_with_cache(
786 &written.pack,
787 po.entry.offset,
788 ObjectFormat::Sha1,
789 |_| panic!("warm cache must not re-walk the chain"),
790 &mut cache,
791 )
792 .expect("test operation should succeed");
793 assert_eq!(
794 warm,
795 (po.object.object_type, po.object.body.len() as u64),
796 "warm cache at offset {}",
797 po.entry.offset
798 );
799 }
800 }
801
802 #[test]
803 fn read_object_at_matches_full_parse_for_ref_delta_pack() {
804 let (base, changed) = similar_blob_objects();
805 let options = delta_pack_options(false);
806 let written = PackFile::write_packed_with_options(
807 &[base, changed.clone()],
808 ObjectFormat::Sha1,
809 &options,
810 )
811 .expect("test operation should succeed");
812 let parsed = PackFile::parse_sha1(&written.pack).expect("test operation should succeed");
813 let by_oid: HashMap<ObjectId, Arc<EncodedObject>> = parsed
814 .entries
815 .iter()
816 .map(|po| (po.entry.oid, Arc::new(po.object.clone())))
817 .collect();
818 for po in &parsed.entries {
819 let got =
820 read_object_at_arc(&written.pack, po.entry.offset, ObjectFormat::Sha1, |oid| {
821 Ok(by_oid.get(oid).cloned())
822 })
823 .expect("test operation should succeed");
824 assert_eq!(*got, po.object);
825 }
826 }
827
828 #[derive(Default)]
832 struct CountingDeltaCache {
833 map: std::cell::RefCell<HashMap<u64, Arc<EncodedObject>>>,
834 hits: std::cell::Cell<usize>,
835 inserts: std::cell::Cell<usize>,
836 }
837
838 impl PackDeltaCache for CountingDeltaCache {
839 fn get(&self, offset: u64) -> Option<Arc<EncodedObject>> {
840 let hit = self.map.borrow().get(&offset).cloned();
841 if hit.is_some() {
842 self.hits.set(self.hits.get() + 1);
843 }
844 hit
845 }
846 fn insert(&self, offset: u64, object: Arc<EncodedObject>) {
847 self.inserts.set(self.inserts.get() + 1);
848 self.map.borrow_mut().insert(offset, object);
849 }
850 }
851
852 #[test]
853 fn read_object_at_with_cache_matches_uncached_and_reuses_bases() {
854 let mut objects = Vec::new();
857 for idx in 0..8u32 {
858 let mut body = vec![b'x'; 4096];
859 body.extend_from_slice(format!("\nvariant {idx}\n").as_bytes());
860 objects.push(EncodedObject::new(ObjectType::Blob, body));
861 }
862 let options = delta_pack_options(true);
863 let written = PackFile::write_packed_with_options(&objects, ObjectFormat::Sha1, &options)
864 .expect("test operation should succeed");
865 let parsed = PackFile::parse_sha1(&written.pack).expect("test operation should succeed");
866
867 let cache = CountingDeltaCache::default();
868 for _ in 0..2 {
871 for po in &parsed.entries {
872 let got = read_object_at_with_cache_arc(
873 &written.pack,
874 po.entry.offset,
875 ObjectFormat::Sha1,
876 |_| Ok(None),
877 &cache,
878 )
879 .expect("test operation should succeed");
880 assert_eq!(*got, po.object, "offset {}", po.entry.offset);
881 }
882 }
883 assert!(cache.hits.get() > 0, "cache never served a warm object");
886 }
887
888 #[test]
889 fn writes_ofs_delta_pack_and_index_that_round_trip() {
890 let (base, changed) = similar_blob_objects();
891 let options = delta_pack_options(true);
892 let written = PackFile::write_packed_with_options(
893 &[base.clone(), changed.clone()],
894 ObjectFormat::Sha1,
895 &options,
896 )
897 .expect("test operation should succeed");
898 let mut second_offset = written.entries[1].offset as usize;
899 let header = parse_entry_header(&written.pack, &mut second_offset)
900 .expect("test operation should succeed");
901 assert_eq!(header.kind, PackObjectKind::OfsDelta);
902
903 let pack = PackFile::parse_sha1(&written.pack).expect("test operation should succeed");
904 let index =
905 PackIndex::parse_v2_sha1(&written.index).expect("test operation should succeed");
906 let oid = changed
907 .object_id(ObjectFormat::Sha1)
908 .expect("test operation should succeed");
909 assert_eq!(pack.entries[0].object, base);
910 assert_eq!(pack.entries[1].object, changed);
911 assert_eq!(index.pack_checksum, pack.checksum);
912 assert_eq!(
913 index
914 .find(&oid)
915 .expect("test operation should succeed")
916 .offset,
917 written.entries[1].offset
918 );
919 }
920
921 #[test]
922 fn resolves_ofs_delta_pack_entry() {
923 let base = b"hello";
924 let result = b"hello world";
925 let pack = two_object_delta_pack(ObjectFormat::Sha1, base, result, DeltaKind::Offset);
926 let parsed = PackFile::parse_sha1(&pack).expect("test operation should succeed");
927 assert_eq!(parsed.entries.len(), 2);
928 assert_eq!(parsed.entries[0].object.body, base);
929 assert_eq!(parsed.entries[1].object.body, result);
930 assert_eq!(
931 parsed.entries[1].entry.oid,
932 sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", result)
933 .expect("test operation should succeed")
934 );
935 }
936
937 #[test]
938 fn resolves_ref_delta_pack_entry() {
939 let base = b"hello";
940 let result = b"hello world";
941 let pack = two_object_delta_pack(ObjectFormat::Sha1, base, result, DeltaKind::Ref);
942 let parsed = PackFile::parse_sha1(&pack).expect("test operation should succeed");
943 assert_eq!(parsed.entries.len(), 2);
944 assert_eq!(parsed.entries[0].object.body, base);
945 assert_eq!(parsed.entries[1].object.body, result);
946 assert_eq!(
947 parsed.entries[1].entry.oid,
948 sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", result)
949 .expect("test operation should succeed")
950 );
951 }
952
953 #[test]
954 fn resolves_thin_ref_delta_pack_entry_with_external_base() {
955 let base = b"hello";
956 let result = b"hello world";
957 let pack = thin_ref_delta_pack(ObjectFormat::Sha1, base, result);
958 assert!(PackFile::parse_sha1(&pack).is_err());
959
960 let base_oid = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", base)
961 .expect("test operation should succeed");
962 let parsed = PackFile::parse_thin(&pack, ObjectFormat::Sha1, |oid| {
963 if oid == &base_oid {
964 Ok(Some(EncodedObject::new(ObjectType::Blob, base.to_vec())))
965 } else {
966 Ok(None)
967 }
968 })
969 .expect("test operation should succeed");
970 assert_eq!(parsed.entries.len(), 1);
971 assert_eq!(parsed.entries[0].object.body, result);
972 assert_eq!(
973 parsed.entries[0].entry.oid,
974 sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", result)
975 .expect("test operation should succeed")
976 );
977 }
978
979 #[test]
980 fn rejects_bad_pack_checksum() {
981 let mut pack = single_object_pack(ObjectFormat::Sha1, ObjectType::Blob, b"hello\n");
982 let last = pack.len() - 1;
983 pack[last] ^= 1;
984 assert!(PackFile::parse_sha1(&pack).is_err());
985 }
986
987 #[test]
988 fn raw_pack_index_rejects_bad_pack_checksum() {
989 let mut pack = single_object_pack(ObjectFormat::Sha1, ObjectType::Blob, b"hello\n");
990 let last = pack.len() - 1;
991 pack[last] ^= 1;
992 assert!(PackIndex::write_v2_for_pack_sha1(&pack).is_err());
993 }
994
995 #[test]
996 fn pack_index_writer_rejects_duplicate_object_ids() {
997 let oid = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"same\n")
998 .expect("test operation should succeed");
999 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1000 .expect("test operation should succeed");
1001 let entries = vec![
1002 PackIndexEntry {
1003 oid,
1004 crc32: 1,
1005 offset: 12,
1006 },
1007 PackIndexEntry {
1008 oid,
1009 crc32: 2,
1010 offset: 24,
1011 },
1012 ];
1013 assert!(PackIndex::write_v2(ObjectFormat::Sha1, &entries, &pack_checksum).is_err());
1014 }
1015
1016 #[test]
1017 fn parses_single_entry_pack_index() {
1018 let oid = ObjectId::from_hex(
1019 ObjectFormat::Sha1,
1020 "ce013625030ba8dba906f756967f9e9ca394464a",
1021 )
1022 .expect("test operation should succeed");
1023 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1024 .expect("test operation should succeed");
1025 let index = single_entry_index(
1026 ObjectFormat::Sha1,
1027 oid,
1028 0x1234_5678,
1029 12,
1030 pack_checksum.clone(),
1031 );
1032 let parsed = PackIndex::parse_v2_sha1(&index).expect("test operation should succeed");
1033 assert_eq!(parsed.version, 2);
1034 assert_eq!(parsed.pack_checksum, pack_checksum);
1035 assert_eq!(parsed.entries.len(), 1);
1036 assert_eq!(
1037 parsed
1038 .find(&oid)
1039 .expect("test operation should succeed")
1040 .offset,
1041 12
1042 );
1043 assert_eq!(
1044 parsed
1045 .find(&oid)
1046 .expect("test operation should succeed")
1047 .crc32,
1048 0x1234_5678
1049 );
1050 assert_pack_index_view_matches_owned(&index, ObjectFormat::Sha1);
1051 }
1052
1053 #[test]
1054 fn parses_single_entry_pack_index_v1() {
1055 let oid = ObjectId::from_hex(
1056 ObjectFormat::Sha1,
1057 "ce013625030ba8dba906f756967f9e9ca394464a",
1058 )
1059 .expect("test operation should succeed");
1060 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1061 .expect("test operation should succeed");
1062 let index =
1063 single_entry_index_v1(ObjectFormat::Sha1, oid, 0x1234_5678, pack_checksum.clone());
1064 let parsed =
1065 PackIndex::parse(&index, ObjectFormat::Sha1).expect("test operation should succeed");
1066 assert_eq!(parsed.version, 1);
1067 assert_eq!(parsed.pack_checksum, pack_checksum);
1068 assert_eq!(parsed.entries.len(), 1);
1069 assert_eq!(
1070 parsed
1071 .find(&oid)
1072 .expect("test operation should succeed")
1073 .offset,
1074 0x1234_5678
1075 );
1076 assert_eq!(
1077 parsed
1078 .find(&oid)
1079 .expect("test operation should succeed")
1080 .crc32,
1081 0
1082 );
1083 assert_pack_index_view_matches_owned(&index, ObjectFormat::Sha1);
1084 }
1085
1086 #[test]
1087 fn rejects_bad_pack_index_v1_checksum() {
1088 let oid = ObjectId::from_hex(
1089 ObjectFormat::Sha1,
1090 "ce013625030ba8dba906f756967f9e9ca394464a",
1091 )
1092 .expect("test operation should succeed");
1093 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1094 .expect("test operation should succeed");
1095 let mut index = single_entry_index_v1(ObjectFormat::Sha1, oid, 12, pack_checksum);
1096 let last = index.len() - 1;
1097 index[last] ^= 1;
1098 assert!(PackIndex::parse(&index, ObjectFormat::Sha1).is_err());
1099 }
1100
1101 #[test]
1102 fn pack_index_view_reads_v2_large_offsets() {
1103 let first = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"large offset a\n")
1104 .expect("test operation should succeed");
1105 let second =
1106 sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"large offset b\n")
1107 .expect("test operation should succeed");
1108 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1109 .expect("test operation should succeed");
1110 let entries = vec![
1111 PackIndexEntry {
1112 oid: first,
1113 crc32: 0x1111_2222,
1114 offset: 0x8000_0000,
1115 },
1116 PackIndexEntry {
1117 oid: second,
1118 crc32: 0x3333_4444,
1119 offset: 0x1_0000_0042,
1120 },
1121 ];
1122 let index = PackIndex::write_v2(ObjectFormat::Sha1, &entries, &pack_checksum)
1123 .expect("test operation should succeed");
1124
1125 assert_pack_index_view_matches_owned(&index, ObjectFormat::Sha1);
1126 let view = PackIndexView::parse(&index, ObjectFormat::Sha1)
1127 .expect("test operation should succeed");
1128 for entry in entries {
1129 assert_eq!(
1130 view.find(&entry.oid),
1131 Some(PackIndexLookup {
1132 crc32: entry.crc32,
1133 offset: entry.offset,
1134 })
1135 );
1136 }
1137 }
1138
1139 #[test]
1140 fn pack_index_view_default_parse_checks_index_checksum() {
1141 let oid = ObjectId::from_hex(
1142 ObjectFormat::Sha1,
1143 "ce013625030ba8dba906f756967f9e9ca394464a",
1144 )
1145 .expect("test operation should succeed");
1146 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1147 .expect("test operation should succeed");
1148 let mut index = single_entry_index(ObjectFormat::Sha1, oid, 0x1234_5678, 12, pack_checksum);
1149 let last = index.len() - 1;
1150 index[last] ^= 1;
1151
1152 assert!(PackIndexView::parse(&index, ObjectFormat::Sha1).is_err());
1153 let view = PackIndexView::parse_without_checksum(&index, ObjectFormat::Sha1)
1154 .expect("test operation should succeed");
1155 let trusted_view = PackIndexViewData::parse_trusted_without_checksum(
1156 Arc::from(index.clone().into_boxed_slice()),
1157 ObjectFormat::Sha1,
1158 )
1159 .expect("test operation should succeed");
1160 assert_eq!(
1161 view.find(&oid),
1162 Some(PackIndexLookup {
1163 crc32: 0x1234_5678,
1164 offset: 12,
1165 })
1166 );
1167 assert_eq!(
1168 trusted_view.find(&oid),
1169 Some(PackIndexLookup {
1170 crc32: 0x1234_5678,
1171 offset: 12,
1172 })
1173 );
1174 }
1175
1176 #[test]
1177 fn reverse_index_resolves_oid_at_offset() {
1178 let objects = (0..3)
1179 .map(|idx| {
1180 EncodedObject::new(
1181 ObjectType::Blob,
1182 format!("reverse index lookup object {idx}\n").into_bytes(),
1183 )
1184 })
1185 .collect::<Vec<_>>();
1186 let written = PackFile::write_packed(&objects, ObjectFormat::Sha1)
1187 .expect("test operation should succeed");
1188 let index = PackIndex::parse(&written.index, ObjectFormat::Sha1)
1189 .expect("test operation should succeed");
1190 let view = PackIndexViewData::parse_trusted_without_checksum(
1191 Arc::from(written.index.clone().into_boxed_slice()),
1192 ObjectFormat::Sha1,
1193 )
1194 .expect("test operation should succeed");
1195 let positions = pack_order_index_positions(&index.entries);
1196 let reverse = PackReverseIndex::parse(
1197 &PackReverseIndex::write(ObjectFormat::Sha1, &positions, &index.pack_checksum)
1198 .expect("test operation should succeed"),
1199 ObjectFormat::Sha1,
1200 index.entries.len(),
1201 )
1202 .expect("test operation should succeed");
1203
1204 for entry in &index.entries {
1205 assert_eq!(
1206 reverse
1207 .oid_at_offset(&view, entry.offset)
1208 .expect("test operation should succeed"),
1209 entry.oid
1210 );
1211 }
1212 assert!(reverse.oid_at_offset(&view, 999).is_none());
1213 }
1214
1215 #[test]
1216 fn parses_pack_reverse_index() {
1217 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1218 .expect("test operation should succeed");
1219 let reverse_index = PackReverseIndex::write(ObjectFormat::Sha1, &[2, 0, 1], &pack_checksum)
1220 .expect("test operation should succeed");
1221 let parsed = PackReverseIndex::parse(&reverse_index, ObjectFormat::Sha1, 3)
1222 .expect("test operation should succeed");
1223 assert_eq!(parsed.version, 1);
1224 assert_eq!(parsed.format, ObjectFormat::Sha1);
1225 assert_eq!(parsed.positions, vec![2, 0, 1]);
1226 assert_eq!(parsed.pack_checksum, pack_checksum);
1227 assert_eq!(
1228 PackReverseIndex::write(ObjectFormat::Sha1, &parsed.positions, &parsed.pack_checksum)
1229 .expect("test operation should succeed"),
1230 reverse_index
1231 );
1232 }
1233
1234 #[test]
1235 fn rejects_bad_pack_reverse_index_checksum() {
1236 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1237 .expect("test operation should succeed");
1238 let mut reverse_index = PackReverseIndex::write(ObjectFormat::Sha1, &[0], &pack_checksum)
1239 .expect("test operation should succeed");
1240 let last = reverse_index.len() - 1;
1241 reverse_index[last] ^= 1;
1242 assert!(PackReverseIndex::parse(&reverse_index, ObjectFormat::Sha1, 1).is_err());
1243 }
1244
1245 #[test]
1246 fn rejects_bad_pack_reverse_index_positions() {
1247 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1248 .expect("test operation should succeed");
1249 let duplicate = pack_reverse_index(ObjectFormat::Sha1, &[0, 0], pack_checksum.clone());
1250 assert!(PackReverseIndex::parse(&duplicate, ObjectFormat::Sha1, 2).is_err());
1251 let out_of_range = pack_reverse_index(ObjectFormat::Sha1, &[0, 2], pack_checksum);
1252 assert!(PackReverseIndex::parse(&out_of_range, ObjectFormat::Sha1, 2).is_err());
1253 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1254 .expect("test operation should succeed");
1255 assert!(PackReverseIndex::write(ObjectFormat::Sha1, &[0, 0], &pack_checksum).is_err());
1256 assert!(PackReverseIndex::write(ObjectFormat::Sha1, &[0, 2], &pack_checksum).is_err());
1257 }
1258
1259 #[test]
1260 fn parses_pack_mtimes() {
1261 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1262 .expect("test operation should succeed");
1263 let mtimes = PackMtimes::write(
1264 ObjectFormat::Sha1,
1265 &[1, 1_700_000_000, u32::MAX],
1266 &pack_checksum,
1267 )
1268 .expect("test operation should succeed");
1269 let parsed = PackMtimes::parse(&mtimes, ObjectFormat::Sha1, 3)
1270 .expect("test operation should succeed");
1271 assert_eq!(parsed.version, 1);
1272 assert_eq!(parsed.format, ObjectFormat::Sha1);
1273 assert_eq!(parsed.mtimes, vec![1, 1_700_000_000, u32::MAX]);
1274 assert_eq!(parsed.pack_checksum, pack_checksum);
1275 assert_eq!(
1276 PackMtimes::write(ObjectFormat::Sha1, &parsed.mtimes, &parsed.pack_checksum)
1277 .expect("test operation should succeed"),
1278 mtimes
1279 );
1280 }
1281
1282 #[test]
1283 fn rejects_bad_pack_mtimes_checksum() {
1284 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1285 .expect("test operation should succeed");
1286 let mut mtimes = PackMtimes::write(ObjectFormat::Sha1, &[1], &pack_checksum)
1287 .expect("test operation should succeed");
1288 let last = mtimes.len() - 1;
1289 mtimes[last] ^= 1;
1290 assert!(PackMtimes::parse(&mtimes, ObjectFormat::Sha1, 1).is_err());
1291 }
1292
1293 #[test]
1294 fn rejects_bad_pack_mtimes_shape() {
1295 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1296 .expect("test operation should succeed");
1297 let mtimes = pack_mtimes(ObjectFormat::Sha1, &[1, 2], pack_checksum.clone());
1298 assert!(PackMtimes::parse(&mtimes, ObjectFormat::Sha1, 1).is_err());
1299
1300 let mut wrong_hash = pack_mtimes(ObjectFormat::Sha1, &[1], pack_checksum);
1301 wrong_hash[11] = 2;
1302 let checksum_offset = wrong_hash.len() - ObjectFormat::Sha1.raw_len();
1303 let checksum = sley_core::digest_bytes(ObjectFormat::Sha1, &wrong_hash[..checksum_offset])
1304 .expect("test operation should succeed");
1305 wrong_hash[checksum_offset..].copy_from_slice(checksum.as_bytes());
1306 assert!(PackMtimes::parse(&wrong_hash, ObjectFormat::Sha1, 1).is_err());
1307 }
1308
1309 #[test]
1310 fn parses_multi_pack_index_header_and_chunk_lookup() {
1311 let first = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"first object\n")
1312 .expect("test operation should succeed");
1313 let second = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"second object\n")
1314 .expect("test operation should succeed");
1315 let chunks = midx_chunks_with_pack_names(
1316 ObjectFormat::Sha1,
1317 b"pack-a.idx\0pack-b.idx\0\0\0".to_vec(),
1318 &[(first.clone(), 0, 12), (second.clone(), 1, 0x1_0000_0000)],
1319 );
1320 let midx = multi_pack_index(ObjectFormat::Sha1, 2, 2, &chunks);
1321 let parsed = MultiPackIndex::parse(&midx, ObjectFormat::Sha1)
1322 .expect("test operation should succeed");
1323 assert_eq!(parsed.version, 2);
1324 assert_eq!(parsed.format, ObjectFormat::Sha1);
1325 assert_eq!(parsed.pack_count, 2);
1326 assert_eq!(parsed.pack_names, vec!["pack-a.idx", "pack-b.idx"]);
1327 assert_eq!(parsed.object_count, 2);
1328 assert_eq!(parsed.objects.len(), 2);
1329 assert_eq!(
1330 parsed
1331 .find(&first)
1332 .expect("test operation should succeed")
1333 .pack_int_id,
1334 0
1335 );
1336 assert_eq!(
1337 parsed
1338 .find(&first)
1339 .expect("test operation should succeed")
1340 .offset,
1341 12
1342 );
1343 assert_eq!(
1344 parsed
1345 .find(&second)
1346 .expect("test operation should succeed")
1347 .pack_int_id,
1348 1
1349 );
1350 assert_eq!(
1351 parsed
1352 .find(&second)
1353 .expect("test operation should succeed")
1354 .offset,
1355 0x1_0000_0000
1356 );
1357 assert_eq!(parsed.reverse_index, None);
1358 assert_eq!(parsed.bitmapped_packs, None);
1359 assert_eq!(parsed.chunks.len(), 5);
1360 assert_eq!(parsed.chunks[0].id, *b"PNAM");
1361 assert_eq!(parsed.chunks[0].offset, 84);
1362 assert_eq!(parsed.chunks[0].len, 24);
1363 assert_eq!(parsed.chunks[1].id, *b"OIDF");
1364 assert_eq!(parsed.chunks[1].offset, 108);
1365 assert_eq!(parsed.chunks[1].len, 1024);
1366 }
1367
1368 #[test]
1369 fn raw_multi_pack_index_lookup_finds_pack_and_offset() {
1370 let first = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"first object\n")
1371 .expect("test operation should succeed");
1372 let second = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"second object\n")
1373 .expect("test operation should succeed");
1374 let missing = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"missing\n")
1375 .expect("test operation should succeed");
1376 let chunks = midx_chunks_with_pack_names(
1377 ObjectFormat::Sha1,
1378 b"pack-a.idx\0pack-b.idx\0\0\0".to_vec(),
1379 &[(first.clone(), 0, 12), (second.clone(), 1, 0x1_0000_0000)],
1380 );
1381 let midx = Arc::new(multi_pack_index(ObjectFormat::Sha1, 2, 2, &chunks));
1382 let lookup = MultiPackIndexOidLookup::parse(midx, ObjectFormat::Sha1)
1383 .expect("test operation should succeed");
1384
1385 assert!(lookup.contains(&first));
1386 assert!(lookup.contains(&second));
1387 assert!(!lookup.contains(&missing));
1388
1389 let first_entry = lookup
1390 .find(&first)
1391 .expect("test operation should succeed")
1392 .expect("object should be present");
1393 assert_eq!(
1394 lookup.pack_name(first_entry.pack_int_id),
1395 Some("pack-a.idx")
1396 );
1397 assert_eq!(first_entry.offset, 12);
1398
1399 let second_entry = lookup
1400 .find(&second)
1401 .expect("test operation should succeed")
1402 .expect("object should be present");
1403 assert_eq!(
1404 lookup.pack_name(second_entry.pack_int_id),
1405 Some("pack-b.idx")
1406 );
1407 assert_eq!(second_entry.offset, 0x1_0000_0000);
1408 assert!(
1409 lookup
1410 .find(&missing)
1411 .expect("test operation should succeed")
1412 .is_none()
1413 );
1414 }
1415
1416 #[test]
1417 fn rejects_bad_multi_pack_index_checksum() {
1418 let chunks = midx_chunks_with_pack_names(ObjectFormat::Sha1, Vec::new(), &[]);
1419 let mut midx = multi_pack_index(ObjectFormat::Sha1, 1, 0, &chunks);
1420 let last = midx.len() - 1;
1421 midx[last] ^= 1;
1422 assert!(MultiPackIndex::parse(&midx, ObjectFormat::Sha1).is_err());
1423 }
1424
1425 #[test]
1426 fn rejects_bad_multi_pack_index_shape() {
1427 let chunks = midx_chunks_with_pack_names(ObjectFormat::Sha1, Vec::new(), &[]);
1428 let mut wrong_hash = multi_pack_index(ObjectFormat::Sha1, 1, 0, &chunks);
1429 wrong_hash[5] = 2;
1430 let checksum_offset = wrong_hash.len() - ObjectFormat::Sha1.raw_len();
1431 let checksum = sley_core::digest_bytes(ObjectFormat::Sha1, &wrong_hash[..checksum_offset])
1432 .expect("test operation should succeed");
1433 wrong_hash[checksum_offset..].copy_from_slice(checksum.as_bytes());
1434 assert!(MultiPackIndex::parse(&wrong_hash, ObjectFormat::Sha1).is_err());
1435
1436 let mut missing_terminator = multi_pack_index(ObjectFormat::Sha1, 1, 0, &chunks);
1437 missing_terminator[12] = b'B';
1438 let checksum_offset = missing_terminator.len() - ObjectFormat::Sha1.raw_len();
1439 let checksum =
1440 sley_core::digest_bytes(ObjectFormat::Sha1, &missing_terminator[..checksum_offset])
1441 .expect("test operation should succeed");
1442 missing_terminator[checksum_offset..].copy_from_slice(checksum.as_bytes());
1443 assert!(MultiPackIndex::parse(&missing_terminator, ObjectFormat::Sha1).is_err());
1444
1445 let mut bad_offset = multi_pack_index(
1446 ObjectFormat::Sha1,
1447 2,
1448 0,
1449 &midx_chunks_with_pack_names(ObjectFormat::Sha1, Vec::new(), &[]),
1450 );
1451 bad_offset[16..24].copy_from_slice(&0u64.to_be_bytes());
1452 let checksum_offset = bad_offset.len() - ObjectFormat::Sha1.raw_len();
1453 let checksum = sley_core::digest_bytes(ObjectFormat::Sha1, &bad_offset[..checksum_offset])
1454 .expect("test operation should succeed");
1455 bad_offset[checksum_offset..].copy_from_slice(checksum.as_bytes());
1456 assert!(MultiPackIndex::parse(&bad_offset, ObjectFormat::Sha1).is_err());
1457 }
1458
1459 #[test]
1460 fn rejects_bad_multi_pack_index_pack_names() {
1461 let missing = multi_pack_index(ObjectFormat::Sha1, 2, 1, &[]);
1462 assert!(MultiPackIndex::parse(&missing, ObjectFormat::Sha1).is_err());
1463
1464 let too_few = multi_pack_index(
1465 ObjectFormat::Sha1,
1466 2,
1467 2,
1468 &midx_chunks_with_pack_names(ObjectFormat::Sha1, b"pack-a.idx\0".to_vec(), &[]),
1469 );
1470 assert!(MultiPackIndex::parse(&too_few, ObjectFormat::Sha1).is_err());
1471
1472 let bad_padding = multi_pack_index(
1473 ObjectFormat::Sha1,
1474 2,
1475 1,
1476 &midx_chunks_with_pack_names(ObjectFormat::Sha1, b"pack-a.idx\0xxxx".to_vec(), &[]),
1477 );
1478 assert!(MultiPackIndex::parse(&bad_padding, ObjectFormat::Sha1).is_err());
1479
1480 let unsorted_v1 = multi_pack_index(
1481 ObjectFormat::Sha1,
1482 1,
1483 2,
1484 &midx_chunks_with_pack_names(
1485 ObjectFormat::Sha1,
1486 b"pack-b.idx\0pack-a.idx\0".to_vec(),
1487 &[],
1488 ),
1489 );
1490 assert!(MultiPackIndex::parse(&unsorted_v1, ObjectFormat::Sha1).is_err());
1491
1492 let unsorted_v2 = multi_pack_index(
1493 ObjectFormat::Sha1,
1494 2,
1495 2,
1496 &midx_chunks_with_pack_names(
1497 ObjectFormat::Sha1,
1498 b"pack-b.idx\0pack-a.idx\0".to_vec(),
1499 &[],
1500 ),
1501 );
1502 let parsed = MultiPackIndex::parse(&unsorted_v2, ObjectFormat::Sha1)
1503 .expect("test operation should succeed");
1504 assert_eq!(parsed.pack_names, vec!["pack-b.idx", "pack-a.idx"]);
1505 }
1506
1507 #[test]
1508 fn rejects_bad_multi_pack_index_object_tables() {
1509 let oid_a = ObjectId::from_hex(
1510 ObjectFormat::Sha1,
1511 "1111111111111111111111111111111111111111",
1512 )
1513 .expect("test operation should succeed");
1514 let oid_b = ObjectId::from_hex(
1515 ObjectFormat::Sha1,
1516 "2222222222222222222222222222222222222222",
1517 )
1518 .expect("test operation should succeed");
1519
1520 let missing_oidf = multi_pack_index(
1521 ObjectFormat::Sha1,
1522 2,
1523 1,
1524 &[(*b"PNAM", b"pack-a.idx\0\0".to_vec())],
1525 );
1526 assert!(MultiPackIndex::parse(&missing_oidf, ObjectFormat::Sha1).is_err());
1527
1528 let bad_fanout = vec![
1529 (*b"PNAM", b"pack-a.idx\0\0".to_vec()),
1530 (*b"OIDF", vec![0; 256 * 4]),
1531 (*b"OIDL", oid_a.as_bytes().to_vec()),
1532 (*b"OOFF", midx_ooff_entries(&[(0, 12)], &mut Vec::new())),
1533 ];
1534 let bad_fanout = multi_pack_index(ObjectFormat::Sha1, 2, 1, &bad_fanout);
1535 assert!(MultiPackIndex::parse(&bad_fanout, ObjectFormat::Sha1).is_err());
1536
1537 let mut unsorted = Vec::new();
1538 unsorted.push((*b"PNAM", b"pack-a.idx\0\0".to_vec()));
1539 unsorted.push((*b"OIDF", midx_oid_fanout(&[oid_a.clone(), oid_b.clone()])));
1540 let mut oid_lookup = Vec::new();
1541 oid_lookup.extend_from_slice(oid_b.as_bytes());
1542 oid_lookup.extend_from_slice(oid_a.as_bytes());
1543 unsorted.push((*b"OIDL", oid_lookup));
1544 unsorted.push((
1545 *b"OOFF",
1546 midx_ooff_entries(&[(0, 12), (0, 24)], &mut Vec::new()),
1547 ));
1548 let unsorted = multi_pack_index(ObjectFormat::Sha1, 2, 1, &unsorted);
1549 assert!(MultiPackIndex::parse(&unsorted, ObjectFormat::Sha1).is_err());
1550
1551 let bad_pack = multi_pack_index(
1552 ObjectFormat::Sha1,
1553 2,
1554 1,
1555 &midx_chunks_with_pack_names(
1556 ObjectFormat::Sha1,
1557 b"pack-a.idx\0\0".to_vec(),
1558 &[(oid_a.clone(), 1, 12)],
1559 ),
1560 );
1561 assert!(MultiPackIndex::parse(&bad_pack, ObjectFormat::Sha1).is_err());
1562
1563 let mut large_offsets = Vec::new();
1564 let missing_loff = vec![
1565 (*b"PNAM", b"pack-a.idx\0\0".to_vec()),
1566 (*b"OIDF", midx_oid_fanout(std::slice::from_ref(&oid_a))),
1567 (*b"OIDL", oid_a.as_bytes().to_vec()),
1568 (
1569 *b"OOFF",
1570 midx_ooff_entries(&[(0, 0x1_0000_0000)], &mut large_offsets),
1571 ),
1572 ];
1573 let missing_loff = multi_pack_index(ObjectFormat::Sha1, 2, 1, &missing_loff);
1574 assert!(MultiPackIndex::parse(&missing_loff, ObjectFormat::Sha1).is_err());
1575
1576 let mut bad_loff =
1577 midx_chunks_with_pack_names(ObjectFormat::Sha1, b"pack-a.idx\0\0".to_vec(), &[]);
1578 bad_loff.push((*b"LOFF", vec![0]));
1579 let bad_loff = multi_pack_index(ObjectFormat::Sha1, 2, 1, &bad_loff);
1580 assert!(MultiPackIndex::parse(&bad_loff, ObjectFormat::Sha1).is_err());
1581 }
1582
1583 #[test]
1584 fn parses_multi_pack_index_bitmap_chunks() {
1585 let first = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"first object\n")
1586 .expect("test operation should succeed");
1587 let second = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"second object\n")
1588 .expect("test operation should succeed");
1589 let mut chunks = midx_chunks_with_pack_names(
1590 ObjectFormat::Sha1,
1591 b"pack-a.idx\0pack-b.idx\0\0\0".to_vec(),
1592 &[(first, 0, 12), (second, 1, 24)],
1593 );
1594 chunks.push((*b"RIDX", midx_u32_table(&[1, 0])));
1595 chunks.push((*b"BTMP", midx_bitmap_packs(&[(0, 1), (1, 1)])));
1596 let midx = multi_pack_index(ObjectFormat::Sha1, 2, 2, &chunks);
1597
1598 let parsed = MultiPackIndex::parse(&midx, ObjectFormat::Sha1)
1599 .expect("test operation should succeed");
1600 assert_eq!(parsed.reverse_index, Some(vec![1, 0]));
1601 assert_eq!(
1602 parsed.bitmapped_packs,
1603 Some(vec![
1604 MultiPackBitmapPack {
1605 bitmap_pos: 0,
1606 bitmap_nr: 1,
1607 },
1608 MultiPackBitmapPack {
1609 bitmap_pos: 1,
1610 bitmap_nr: 1,
1611 },
1612 ])
1613 );
1614 }
1615
1616 #[test]
1617 fn writes_multi_pack_index_that_round_trips() {
1618 let first = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"first object\n")
1619 .expect("test operation should succeed");
1620 let second = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"second object\n")
1621 .expect("test operation should succeed");
1622 let bytes = MultiPackIndex::write(
1623 ObjectFormat::Sha1,
1624 2,
1625 &["pack-b.idx".into(), "pack-a.idx".into()],
1626 &[
1627 MultiPackIndexEntry {
1628 oid: second.clone(),
1629 pack_int_id: 0,
1630 offset: 0x1_0000_0000,
1631 force_large_offset: false,
1632 },
1633 MultiPackIndexEntry {
1634 oid: first.clone(),
1635 pack_int_id: 1,
1636 offset: 12,
1637 force_large_offset: false,
1638 },
1639 ],
1640 )
1641 .expect("test operation should succeed");
1642
1643 let parsed = MultiPackIndex::parse(&bytes, ObjectFormat::Sha1)
1644 .expect("test operation should succeed");
1645 assert_eq!(parsed.version, 2);
1646 assert_eq!(parsed.pack_names, vec!["pack-b.idx", "pack-a.idx"]);
1647 assert_eq!(parsed.object_count, 2);
1648 assert_eq!(
1649 parsed
1650 .find(&first)
1651 .expect("test operation should succeed")
1652 .pack_int_id,
1653 1
1654 );
1655 assert_eq!(
1656 parsed
1657 .find(&first)
1658 .expect("test operation should succeed")
1659 .offset,
1660 12
1661 );
1662 assert_eq!(
1663 parsed
1664 .find(&second)
1665 .expect("test operation should succeed")
1666 .pack_int_id,
1667 0
1668 );
1669 assert_eq!(
1670 parsed
1671 .find(&second)
1672 .expect("test operation should succeed")
1673 .offset,
1674 0x1_0000_0000
1675 );
1676 assert!(parsed.chunks.iter().any(|chunk| chunk.id == *b"LOFF"));
1677 }
1678
1679 #[test]
1680 fn write_multi_pack_index_rejects_invalid_inputs() {
1681 let oid = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"object\n")
1682 .expect("test operation should succeed");
1683 assert!(MultiPackIndex::write(ObjectFormat::Sha1, 3, &["pack-a.idx".into()], &[]).is_err());
1684 assert!(
1685 MultiPackIndex::write(
1686 ObjectFormat::Sha1,
1687 1,
1688 &["pack-b.idx".into(), "pack-a.idx".into()],
1689 &[],
1690 )
1691 .is_err()
1692 );
1693 assert!(MultiPackIndex::write(ObjectFormat::Sha1, 2, &["pack/a.idx".into()], &[]).is_err());
1694 assert!(
1695 MultiPackIndex::write(
1696 ObjectFormat::Sha1,
1697 2,
1698 &["pack-a.idx".into()],
1699 &[MultiPackIndexEntry {
1700 oid,
1701 pack_int_id: 1,
1702 offset: 12,
1703 force_large_offset: false,
1704 }],
1705 )
1706 .is_err()
1707 );
1708 assert!(
1709 MultiPackIndex::write(
1710 ObjectFormat::Sha1,
1711 2,
1712 &["pack-a.idx".into()],
1713 &[
1714 MultiPackIndexEntry {
1715 oid,
1716 pack_int_id: 0,
1717 offset: 12,
1718 force_large_offset: false,
1719 },
1720 MultiPackIndexEntry {
1721 oid,
1722 pack_int_id: 0,
1723 offset: 24,
1724 force_large_offset: false,
1725 },
1726 ],
1727 )
1728 .is_err()
1729 );
1730 }
1731
1732 #[test]
1733 fn rejects_bad_multi_pack_index_bitmap_chunks() {
1734 let oid_a = ObjectId::from_hex(
1735 ObjectFormat::Sha1,
1736 "1111111111111111111111111111111111111111",
1737 )
1738 .expect("test operation should succeed");
1739 let oid_b = ObjectId::from_hex(
1740 ObjectFormat::Sha1,
1741 "2222222222222222222222222222222222222222",
1742 )
1743 .expect("test operation should succeed");
1744
1745 let mut duplicate_ridx = midx_chunks_with_pack_names(
1746 ObjectFormat::Sha1,
1747 b"pack-a.idx\0\0".to_vec(),
1748 &[(oid_a.clone(), 0, 12), (oid_b.clone(), 0, 24)],
1749 );
1750 duplicate_ridx.push((*b"RIDX", midx_u32_table(&[0, 0])));
1751 let duplicate_ridx = multi_pack_index(ObjectFormat::Sha1, 2, 1, &duplicate_ridx);
1752 assert!(MultiPackIndex::parse(&duplicate_ridx, ObjectFormat::Sha1).is_err());
1753
1754 let mut short_btmp = midx_chunks_with_pack_names(
1755 ObjectFormat::Sha1,
1756 b"pack-a.idx\0pack-b.idx\0\0\0".to_vec(),
1757 &[(oid_a.clone(), 0, 12), (oid_b.clone(), 1, 24)],
1758 );
1759 short_btmp.push((*b"BTMP", midx_bitmap_packs(&[(0, 1)])));
1760 let short_btmp = multi_pack_index(ObjectFormat::Sha1, 2, 2, &short_btmp);
1761 assert!(MultiPackIndex::parse(&short_btmp, ObjectFormat::Sha1).is_err());
1762
1763 let mut out_of_range_btmp = midx_chunks_with_pack_names(
1764 ObjectFormat::Sha1,
1765 b"pack-a.idx\0\0".to_vec(),
1766 &[(oid_a, 0, 12), (oid_b, 0, 24)],
1767 );
1768 out_of_range_btmp.push((*b"BTMP", midx_bitmap_packs(&[(1, 2)])));
1769 let out_of_range_btmp = multi_pack_index(ObjectFormat::Sha1, 2, 1, &out_of_range_btmp);
1770 assert!(MultiPackIndex::parse(&out_of_range_btmp, ObjectFormat::Sha1).is_err());
1771 }
1772
1773 #[test]
1774 fn parses_pack_bitmap_index_with_hash_cache() {
1775 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1776 .expect("test operation should succeed");
1777 let bitmap = pack_bitmap_index(
1778 ObjectFormat::Sha1,
1779 3,
1780 PackBitmapIndex::OPTION_FULL_DAG | PackBitmapIndex::OPTION_HASH_CACHE,
1781 &pack_checksum,
1782 &[(2, 0, 1, &[0b101])],
1783 Some(&[0x1111_1111, 0x2222_2222, 0x3333_3333]),
1784 );
1785
1786 let parsed = PackBitmapIndex::parse(&bitmap, ObjectFormat::Sha1, 3)
1787 .expect("test operation should succeed");
1788 assert_eq!(parsed.version, 1);
1789 assert_eq!(parsed.format, ObjectFormat::Sha1);
1790 assert_eq!(
1791 parsed.options,
1792 PackBitmapIndex::OPTION_FULL_DAG | PackBitmapIndex::OPTION_HASH_CACHE
1793 );
1794 assert_eq!(parsed.pack_checksum, pack_checksum);
1795 assert_eq!(parsed.type_bitmaps.commits.bit_size, 3);
1796 assert_eq!(parsed.type_bitmaps.trees.bit_size, 3);
1797 assert_eq!(parsed.entries.len(), 1);
1798 let entry = parsed
1799 .entry_for_index_position(2)
1800 .expect("test operation should succeed");
1801 assert_eq!(entry.xor_offset, 0);
1802 assert_eq!(entry.flags, 1);
1803 assert_eq!(entry.bitmap.words, ewah_literal_words(&[0b101]));
1804 assert_eq!(
1805 parsed.name_hash_cache,
1806 Some(vec![0x1111_1111, 0x2222_2222, 0x3333_3333])
1807 );
1808 }
1809
1810 #[test]
1811 fn parses_pack_bitmap_index_sha256() {
1812 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha256, b"pack")
1813 .expect("test operation should succeed");
1814 let bitmap = pack_bitmap_index(
1815 ObjectFormat::Sha256,
1816 2,
1817 PackBitmapIndex::OPTION_FULL_DAG,
1818 &pack_checksum,
1819 &[(0, 0, 0, &[0b11])],
1820 None,
1821 );
1822
1823 let parsed = PackBitmapIndex::parse(&bitmap, ObjectFormat::Sha256, 2)
1824 .expect("test operation should succeed");
1825 assert_eq!(parsed.version, 1);
1826 assert_eq!(parsed.format, ObjectFormat::Sha256);
1827 assert_eq!(parsed.pack_checksum, pack_checksum);
1828 assert_eq!(parsed.index_checksum.format(), ObjectFormat::Sha256);
1829 assert_eq!(parsed.entries[0].object_position, 0);
1830 assert_eq!(parsed.name_hash_cache, None);
1831 }
1832
1833 #[test]
1834 fn parses_upstream_git_written_pack_bitmap_index() {
1835 let root = unique_temp_dir("git-pack-bitmap-upstream");
1836 fs::create_dir_all(&root).expect("test operation should succeed");
1837 {
1838 run_git_success(&root, &["init", "-q", "-b", "main"]);
1839 run_git_success(
1840 &root,
1841 &[
1842 "-c",
1843 "user.name=Example User",
1844 "-c",
1845 "user.email=example@example.invalid",
1846 "commit",
1847 "--allow-empty",
1848 "-q",
1849 "-m",
1850 "one",
1851 ],
1852 );
1853 run_git_success(
1854 &root,
1855 &[
1856 "-c",
1857 "user.name=Example User",
1858 "-c",
1859 "user.email=example@example.invalid",
1860 "commit",
1861 "--allow-empty",
1862 "-q",
1863 "-m",
1864 "two",
1865 ],
1866 );
1867 run_git_success(&root, &["repack", "-adb"]);
1868 let pack_dir = root.join(".git").join("objects").join("pack");
1869 let idx_path = single_path_with_extension(&pack_dir, "idx");
1870 let bitmap_path = single_path_with_extension(&pack_dir, "bitmap");
1871 let index = PackIndex::parse(
1872 &fs::read(idx_path).expect("test operation should succeed"),
1873 ObjectFormat::Sha1,
1874 )
1875 .expect("test operation should succeed");
1876 let bitmap = PackBitmapIndex::parse(
1877 &fs::read(bitmap_path).expect("test operation should succeed"),
1878 ObjectFormat::Sha1,
1879 index.entries.len(),
1880 )
1881 .expect("test operation should succeed");
1882 assert_eq!(bitmap.pack_checksum, index.pack_checksum);
1883 assert!(!bitmap.entries.is_empty());
1884 };
1885 let _ = fs::remove_dir_all(&root);
1886 }
1887
1888 #[test]
1889 fn rejects_bad_pack_bitmap_index_header_and_checksum() {
1890 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1891 .expect("test operation should succeed");
1892 let bitmap = pack_bitmap_index(
1893 ObjectFormat::Sha1,
1894 1,
1895 PackBitmapIndex::OPTION_FULL_DAG,
1896 &pack_checksum,
1897 &[(0, 0, 0, &[1])],
1898 None,
1899 );
1900
1901 let mut bad_signature = bitmap.clone();
1902 bad_signature[0] = b'X';
1903 assert!(PackBitmapIndex::parse(&bad_signature, ObjectFormat::Sha1, 1).is_err());
1904
1905 let mut bad_version = bitmap.clone();
1906 bad_version[5] = 2;
1907 refresh_trailing_checksum(ObjectFormat::Sha1, &mut bad_version);
1908 assert!(PackBitmapIndex::parse(&bad_version, ObjectFormat::Sha1, 1).is_err());
1909
1910 let mut bad_option = bitmap.clone();
1911 bad_option[7] = 0x20;
1912 refresh_trailing_checksum(ObjectFormat::Sha1, &mut bad_option);
1913 assert!(PackBitmapIndex::parse(&bad_option, ObjectFormat::Sha1, 1).is_err());
1914
1915 let mut bad_checksum = bitmap;
1916 let last = bad_checksum.len() - 1;
1917 bad_checksum[last] ^= 1;
1918 assert!(PackBitmapIndex::parse(&bad_checksum, ObjectFormat::Sha1, 1).is_err());
1919 }
1920
1921 #[test]
1922 fn rejects_bad_pack_bitmap_index_ewah_and_entries() {
1923 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1924 .expect("test operation should succeed");
1925 let bitmap = pack_bitmap_index(
1926 ObjectFormat::Sha1,
1927 2,
1928 PackBitmapIndex::OPTION_FULL_DAG,
1929 &pack_checksum,
1930 &[(0, 0, 0, &[0b01]), (1, 1, 0, &[0b11])],
1931 None,
1932 );
1933
1934 let mut truncated = bitmap.clone();
1935 truncated.truncate(truncated.len() - ObjectFormat::Sha1.raw_len() - 1);
1936 refresh_trailing_checksum(ObjectFormat::Sha1, &mut truncated);
1937 assert!(PackBitmapIndex::parse(&truncated, ObjectFormat::Sha1, 2).is_err());
1938
1939 let mut out_of_range_position = pack_bitmap_index(
1940 ObjectFormat::Sha1,
1941 2,
1942 PackBitmapIndex::OPTION_FULL_DAG,
1943 &pack_checksum,
1944 &[(2, 0, 0, &[0b01])],
1945 None,
1946 );
1947 assert!(PackBitmapIndex::parse(&out_of_range_position, ObjectFormat::Sha1, 2).is_err());
1948 refresh_trailing_checksum(ObjectFormat::Sha1, &mut out_of_range_position);
1949 assert!(PackBitmapIndex::parse(&out_of_range_position, ObjectFormat::Sha1, 2).is_err());
1950
1951 let invalid_xor = pack_bitmap_index(
1952 ObjectFormat::Sha1,
1953 2,
1954 PackBitmapIndex::OPTION_FULL_DAG,
1955 &pack_checksum,
1956 &[(0, 1, 0, &[0b01])],
1957 None,
1958 );
1959 assert!(PackBitmapIndex::parse(&invalid_xor, ObjectFormat::Sha1, 2).is_err());
1960 }
1961
1962 #[test]
1963 fn parses_single_entry_pack_index_sha256() {
1964 let oid = sley_core::object_id_for_bytes(ObjectFormat::Sha256, "blob", b"hello sha256\n")
1965 .expect("test operation should succeed");
1966 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha256, b"pack")
1967 .expect("test operation should succeed");
1968 let index = single_entry_index(
1969 ObjectFormat::Sha256,
1970 oid,
1971 0x1234_5678,
1972 12,
1973 pack_checksum.clone(),
1974 );
1975 let parsed =
1976 PackIndex::parse(&index, ObjectFormat::Sha256).expect("test operation should succeed");
1977 assert_eq!(parsed.version, 2);
1978 assert_eq!(parsed.pack_checksum, pack_checksum);
1979 assert_eq!(parsed.entries.len(), 1);
1980 assert_eq!(
1981 parsed
1982 .find(&oid)
1983 .expect("test operation should succeed")
1984 .offset,
1985 12
1986 );
1987 assert_eq!(
1988 parsed
1989 .find(&oid)
1990 .expect("test operation should succeed")
1991 .crc32,
1992 0x1234_5678
1993 );
1994 assert_eq!(parsed.index_checksum.format(), ObjectFormat::Sha256);
1995 assert_pack_index_view_matches_owned(&index, ObjectFormat::Sha256);
1996 }
1997
1998 #[test]
1999 fn write_packed_deltifies_similar_blobs_and_round_trips_sha1() {
2000 write_packed_deltifies_similar_blobs_and_round_trips(ObjectFormat::Sha1);
2001 }
2002
2003 #[test]
2004 fn write_packed_deltifies_similar_blobs_and_round_trips_sha256() {
2005 write_packed_deltifies_similar_blobs_and_round_trips(ObjectFormat::Sha256);
2006 }
2007
2008 #[test]
2009 fn write_packed_rejects_duplicate_objects() {
2010 let object = EncodedObject::new(ObjectType::Blob, b"same\n".to_vec());
2011 assert!(PackFile::write_packed(&[object.clone(), object], ObjectFormat::Sha1,).is_err());
2012 }
2013
2014 #[test]
2015 fn write_packed_with_known_ids_validates_ids_before_trusting_them() {
2016 let object = EncodedObject::new(ObjectType::Blob, b"same\n".to_vec());
2017 let sha1 = object
2018 .object_id(ObjectFormat::Sha1)
2019 .expect("test operation should succeed");
2020 let sha256 = object
2021 .object_id(ObjectFormat::Sha256)
2022 .expect("test operation should succeed");
2023 let duplicate = [
2024 PackInput {
2025 oid: &sha1,
2026 object: &object,
2027 },
2028 PackInput {
2029 oid: &sha1,
2030 object: &object,
2031 },
2032 ];
2033 assert!(PackFile::write_packed_with_known_ids(&duplicate, ObjectFormat::Sha1).is_err());
2034
2035 let wrong_format = [PackInput {
2036 oid: &sha256,
2037 object: &object,
2038 }];
2039 assert!(PackFile::write_packed_with_known_ids(&wrong_format, ObjectFormat::Sha1).is_err());
2040 }
2041
2042 #[test]
2043 fn write_packed_with_known_ids_to_writer_matches_in_memory_pack() {
2044 let objects = similar_blob_family(6);
2045 let object_ids = objects
2046 .iter()
2047 .map(|object| {
2048 object
2049 .object_id(ObjectFormat::Sha1)
2050 .expect("test operation should succeed")
2051 })
2052 .collect::<Vec<_>>();
2053 let inputs = objects
2054 .iter()
2055 .zip(&object_ids)
2056 .map(|(object, oid)| PackInput { oid, object })
2057 .collect::<Vec<_>>();
2058 let options = PackWriteOptions::new();
2059 let in_memory = PackFile::write_packed_with_known_ids_and_options(
2060 &inputs,
2061 ObjectFormat::Sha1,
2062 &options,
2063 )
2064 .expect("test operation should succeed");
2065 let mut written = Vec::new();
2066 let streamed = PackFile::write_packed_with_known_ids_to_writer(
2067 &inputs,
2068 ObjectFormat::Sha1,
2069 &options,
2070 &mut written,
2071 )
2072 .expect("test operation should succeed");
2073
2074 assert_eq!(written, in_memory.pack);
2075 assert_eq!(streamed.index, in_memory.index);
2076 assert_eq!(streamed.checksum, in_memory.checksum);
2077 assert_eq!(streamed.entries, in_memory.entries);
2078 assert_eq!(streamed.delta_count, in_memory.delta_count);
2079 assert_eq!(streamed.pack_size, in_memory.pack.len() as u64);
2080 }
2081
2082 #[test]
2083 fn write_packed_from_source_to_writer_deltifies_across_windows() {
2084 let format = ObjectFormat::Sha1;
2085 let mut objects = Vec::new();
2086 for idx in 0..PACK_STREAM_COMPRESSION_WINDOW_OBJECTS - 1 {
2087 objects.push(EncodedObject::new(
2088 ObjectType::Blob,
2089 format!("unrelated streamed source object {idx:04}\n").into_bytes(),
2090 ));
2091 }
2092 let base_body = b"cross-window base payload with enough shared anchors\nbase\n".to_vec();
2093 let target_body =
2094 b"cross-window base payload with enough shared anchors\ntarget\n".to_vec();
2095 objects.push(EncodedObject::new(ObjectType::Blob, base_body));
2096 objects.push(EncodedObject::new(ObjectType::Blob, target_body));
2097
2098 let object_ids = objects
2099 .iter()
2100 .map(|object| {
2101 object
2102 .object_id(format)
2103 .expect("test operation should succeed")
2104 })
2105 .collect::<Vec<_>>();
2106 let base_oid = object_ids[PACK_STREAM_COMPRESSION_WINDOW_OBJECTS - 1];
2107 let target_oid = object_ids[PACK_STREAM_COMPRESSION_WINDOW_OBJECTS];
2108 let object_map = object_ids
2109 .iter()
2110 .copied()
2111 .zip(objects.into_iter().map(Arc::new))
2112 .collect::<HashMap<_, _>>();
2113
2114 let options = PackWriteOptions::new().with_reorder(false).with_window(10);
2115 let mut written = Vec::new();
2116 let summary = PackFile::write_packed_from_source_to_writer(
2117 &object_ids,
2118 format,
2119 &options,
2120 |oid| {
2121 object_map
2122 .get(oid)
2123 .cloned()
2124 .ok_or_else(|| GitError::not_found(format!("missing test object {oid}")))
2125 },
2126 &mut written,
2127 )
2128 .expect("test operation should succeed");
2129
2130 assert!(
2131 summary.delta_count > 0,
2132 "expected source-backed streaming writer to find deltas"
2133 );
2134 let stats =
2135 PackFile::verify_pack_stats(&written, format).expect("test operation should succeed");
2136 let target = stats
2137 .objects
2138 .iter()
2139 .find(|entry| entry.oid == target_oid)
2140 .expect("target object should be present");
2141 assert_eq!(target.base_oid, Some(base_oid));
2142 }
2143
2144 fn write_packed_deltifies_similar_blobs_and_round_trips(format: ObjectFormat) {
2145 let objects = similar_blob_family(8);
2146 let packed =
2147 PackFile::write_packed(&objects, format).expect("test operation should succeed");
2148 let undeltified =
2149 PackFile::write_undeltified(&objects, format).expect("test operation should succeed");
2150
2151 assert!(
2154 packed.pack.len() < undeltified.pack.len(),
2155 "expected delta pack ({}) smaller than undeltified pack ({})",
2156 packed.pack.len(),
2157 undeltified.pack.len()
2158 );
2159
2160 let kinds = pack_entry_kinds(&packed.pack, format);
2162 let delta_count = kinds
2163 .iter()
2164 .filter(|kind| matches!(kind, PackObjectKind::OfsDelta | PackObjectKind::RefDelta))
2165 .count();
2166 assert!(
2167 delta_count >= 1,
2168 "expected at least one delta entry, found kinds {kinds:?}"
2169 );
2170
2171 let parsed = PackFile::parse(&packed.pack, format).expect("test operation should succeed");
2173 assert_eq!(parsed.entries.len(), objects.len());
2174 for object in &objects {
2175 let oid = object
2176 .object_id(format)
2177 .expect("test operation should succeed");
2178 let found = parsed
2179 .entries
2180 .iter()
2181 .find(|entry| entry.entry.oid == oid)
2182 .unwrap_or_else(|| panic!("object {oid} missing from parsed pack"));
2183 assert_eq!(&found.object, object, "object {oid} did not round-trip");
2184 }
2185
2186 let index = PackIndex::parse(&packed.index, format).expect("test operation should succeed");
2188 assert_eq!(index.pack_checksum, packed.checksum);
2189 for object in &objects {
2190 let oid = object
2191 .object_id(format)
2192 .expect("test operation should succeed");
2193 assert!(index.find(&oid).is_some(), "index missing {oid}");
2194 }
2195 }
2196
2197 #[test]
2198 fn write_packed_emits_ofs_delta_by_default() {
2199 let objects = similar_blob_family(6);
2200 let packed = PackFile::write_packed(&objects, ObjectFormat::Sha1)
2201 .expect("test operation should succeed");
2202 let kinds = pack_entry_kinds(&packed.pack, ObjectFormat::Sha1);
2203 assert!(
2204 kinds.contains(&PackObjectKind::OfsDelta),
2205 "expected an ofs-delta entry by default, found {kinds:?}"
2206 );
2207 assert!(
2208 !kinds.contains(&PackObjectKind::RefDelta),
2209 "default self-contained pack must not use ref-delta, found {kinds:?}"
2210 );
2211 assert!(PackFile::parse(&packed.pack, ObjectFormat::Sha1).is_ok());
2213 }
2214
2215 #[test]
2216 fn write_packed_can_emit_ref_delta() {
2217 let objects = similar_blob_family(6);
2218 let options = PackWriteOptions::new().with_prefer_ofs_delta(false);
2219 let packed = PackFile::write_packed_with_options(&objects, ObjectFormat::Sha1, &options)
2220 .expect("test operation should succeed");
2221 let kinds = pack_entry_kinds(&packed.pack, ObjectFormat::Sha1);
2222 assert!(
2223 kinds.contains(&PackObjectKind::RefDelta),
2224 "expected a ref-delta entry, found {kinds:?}"
2225 );
2226 assert!(
2227 !kinds.contains(&PackObjectKind::OfsDelta),
2228 "ref-delta mode must not emit ofs-delta, found {kinds:?}"
2229 );
2230
2231 let parsed = PackFile::parse(&packed.pack, ObjectFormat::Sha1)
2234 .expect("test operation should succeed");
2235 assert_eq!(parsed.entries.len(), objects.len());
2236 }
2237
2238 #[test]
2239 fn write_packed_bounds_delta_chain_depth() {
2240 let objects = incremental_blob_chain(20);
2244 let format = ObjectFormat::Sha1;
2245
2246 for max_depth in [1usize, 2, 5] {
2247 let options = PackWriteOptions::new()
2248 .with_window(20)
2249 .with_depth(max_depth);
2250 let packed = PackFile::write_packed_with_options(&objects, format, &options)
2251 .expect("test operation should succeed");
2252
2253 let depths = pack_entry_depths(&packed.pack, format);
2254 let observed = depths.iter().copied().max().unwrap_or(0);
2255 assert!(
2256 observed <= max_depth,
2257 "max chain depth {observed} exceeded bound {max_depth}"
2258 );
2259
2260 let parsed =
2262 PackFile::parse(&packed.pack, format).expect("test operation should succeed");
2263 for object in &objects {
2264 let oid = object
2265 .object_id(format)
2266 .expect("test operation should succeed");
2267 let found = parsed
2268 .entries
2269 .iter()
2270 .find(|entry| entry.entry.oid == oid)
2271 .expect("test operation should succeed");
2272 assert_eq!(&found.object, object);
2273 }
2274 }
2275 }
2276
2277 #[test]
2278 fn write_packed_depth_zero_stores_everything_undeltified() {
2279 let objects = similar_blob_family(5);
2280 let options = PackWriteOptions::new().with_depth(0);
2281 let packed = PackFile::write_packed_with_options(&objects, ObjectFormat::Sha1, &options)
2282 .expect("test operation should succeed");
2283 let kinds = pack_entry_kinds(&packed.pack, ObjectFormat::Sha1);
2284 assert!(
2285 kinds
2286 .iter()
2287 .all(|kind| !matches!(kind, PackObjectKind::OfsDelta | PackObjectKind::RefDelta)),
2288 "depth 0 must disable deltas, found {kinds:?}"
2289 );
2290 }
2291
2292 #[test]
2293 fn write_thin_uses_external_base_and_round_trips_sha1() {
2294 write_thin_uses_external_base_and_round_trips(ObjectFormat::Sha1);
2295 }
2296
2297 #[test]
2298 fn write_thin_uses_external_base_and_round_trips_sha256() {
2299 write_thin_uses_external_base_and_round_trips(ObjectFormat::Sha256);
2300 }
2301
2302 fn write_thin_uses_external_base_and_round_trips(format: ObjectFormat) {
2303 let base = blob_with_marker("EXTERNAL-BASE");
2306 let target = blob_with_marker("EXTERNAL-TARGET");
2307 let base_oid = base
2308 .object_id(format)
2309 .expect("test operation should succeed");
2310
2311 let mut external = HashMap::new();
2312 external.insert(base_oid, base.clone());
2313 let packed = PackFile::write_thin(std::slice::from_ref(&target), format, external)
2314 .expect("test operation should succeed");
2315
2316 let kinds = pack_entry_kinds(&packed.pack, format);
2318 assert_eq!(kinds, vec![PackObjectKind::RefDelta]);
2319
2320 let mut offset = 12usize;
2322 let header =
2323 parse_entry_header(&packed.pack, &mut offset).expect("test operation should succeed");
2324 assert_eq!(header.kind, PackObjectKind::RefDelta);
2325 let referenced =
2326 ObjectId::from_raw(format, &packed.pack[offset..offset + format.raw_len()])
2327 .expect("test operation should succeed");
2328 assert_eq!(referenced, base_oid);
2329
2330 assert!(PackFile::parse(&packed.pack, format).is_err());
2332
2333 let parsed = PackFile::parse_thin(&packed.pack, format, |oid| {
2335 if oid == &base_oid {
2336 Ok(Some(base.clone()))
2337 } else {
2338 Ok(None)
2339 }
2340 })
2341 .expect("test operation should succeed");
2342 assert_eq!(parsed.entries.len(), 1);
2343 assert_eq!(parsed.entries[0].object, target);
2344 }
2345
2346 #[test]
2347 fn write_packed_preserves_distinct_objects_with_no_similarity() {
2348 let objects = vec![
2351 EncodedObject::new(ObjectType::Blob, b"alpha distinct\n".to_vec()),
2352 EncodedObject::new(ObjectType::Tree, vec![0u8; 0]),
2353 EncodedObject::new(ObjectType::Commit, b"tree 0000\n".to_vec()),
2354 ];
2355 let format = ObjectFormat::Sha1;
2356 let packed =
2357 PackFile::write_packed(&objects, format).expect("test operation should succeed");
2358 let parsed = PackFile::parse(&packed.pack, format).expect("test operation should succeed");
2359 assert_eq!(parsed.entries.len(), objects.len());
2360 for object in &objects {
2361 let oid = object
2362 .object_id(format)
2363 .expect("test operation should succeed");
2364 assert!(parsed.entries.iter().any(|entry| entry.entry.oid == oid));
2365 }
2366 }
2367
2368 fn similar_blob_family(count: usize) -> Vec<EncodedObject> {
2372 let mut common_head = Vec::new();
2373 for _ in 0..200 {
2374 common_head.extend_from_slice(b"shared header line for delta testing\n");
2375 }
2376 let mut common_tail = Vec::new();
2377 for _ in 0..200 {
2378 common_tail.extend_from_slice(b"shared trailer line for delta testing\n");
2379 }
2380 (0..count)
2381 .map(|idx| {
2382 let mut body = common_head.clone();
2383 body.extend_from_slice(format!("UNIQUE MIDDLE MARKER NUMBER {idx}\n").as_bytes());
2384 body.extend_from_slice(&common_tail);
2385 EncodedObject::new(ObjectType::Blob, body)
2386 })
2387 .collect()
2388 }
2389
2390 fn incremental_blob_chain(count: usize) -> Vec<EncodedObject> {
2393 let mut body = Vec::new();
2394 for _ in 0..100 {
2395 body.extend_from_slice(b"baseline content shared across the whole chain\n");
2396 }
2397 let mut objects = Vec::with_capacity(count);
2398 for idx in 0..count {
2399 body.extend_from_slice(format!("appended unique line {idx}\n").as_bytes());
2400 objects.push(EncodedObject::new(ObjectType::Blob, body.clone()));
2401 }
2402 objects
2403 }
2404
2405 fn blob_with_marker(marker: &str) -> EncodedObject {
2406 let mut body = Vec::new();
2407 for _ in 0..150 {
2408 body.extend_from_slice(b"common body shared between base and target\n");
2409 }
2410 body.extend_from_slice(marker.as_bytes());
2411 body.push(b'\n');
2412 for _ in 0..150 {
2413 body.extend_from_slice(b"more common body shared between objects\n");
2414 }
2415 EncodedObject::new(ObjectType::Blob, body)
2416 }
2417
2418 fn pack_entry_kinds(pack: &[u8], format: ObjectFormat) -> Vec<PackObjectKind> {
2420 pack_entry_descriptors(pack, format)
2421 .into_iter()
2422 .map(|descriptor| descriptor.kind)
2423 .collect()
2424 }
2425
2426 fn pack_entry_depths(pack: &[u8], format: ObjectFormat) -> Vec<usize> {
2430 let descriptors = pack_entry_descriptors(pack, format);
2431 let mut depth_by_offset: HashMap<u64, usize> = HashMap::new();
2432 let mut depths = Vec::with_capacity(descriptors.len());
2433 for descriptor in &descriptors {
2434 let depth = match &descriptor.base {
2435 EntryBase::None => 0,
2436 EntryBase::Offset(base_offset) => {
2437 depth_by_offset.get(base_offset).copied().unwrap_or(0) + 1
2438 }
2439 EntryBase::Ref => 1,
2443 };
2444 depth_by_offset.insert(descriptor.offset, depth);
2445 depths.push(depth);
2446 }
2447 depths
2448 }
2449
2450 struct EntryDescriptor {
2451 offset: u64,
2452 kind: PackObjectKind,
2453 base: EntryBase,
2454 }
2455
2456 enum EntryBase {
2457 None,
2458 Offset(u64),
2459 Ref,
2460 }
2461
2462 fn pack_entry_descriptors(pack: &[u8], format: ObjectFormat) -> Vec<EntryDescriptor> {
2463 let trailer_offset = pack.len() - format.raw_len();
2464 let count = u32_be(&pack[8..12]) as usize;
2465 let mut offset = 12usize;
2466 let mut descriptors = Vec::with_capacity(count);
2467 for _ in 0..count {
2468 let entry_offset = offset as u64;
2469 let header =
2470 parse_entry_header(pack, &mut offset).expect("test operation should succeed");
2471 let base = match header.kind {
2472 PackObjectKind::OfsDelta => {
2473 let base_offset = parse_ofs_delta_base_offset(pack, &mut offset, entry_offset)
2474 .expect("test operation should succeed");
2475 EntryBase::Offset(base_offset)
2476 }
2477 PackObjectKind::RefDelta => {
2478 offset += format.raw_len();
2479 EntryBase::Ref
2480 }
2481 _ => EntryBase::None,
2482 };
2483 let mut decoder = ZlibDecoder::new(&pack[offset..trailer_offset]);
2484 let mut body = Vec::new();
2485 decoder
2486 .read_to_end(&mut body)
2487 .expect("test operation should succeed");
2488 offset += decoder.total_in() as usize;
2489 descriptors.push(EntryDescriptor {
2490 offset: entry_offset,
2491 kind: header.kind,
2492 base,
2493 });
2494 }
2495 descriptors
2496 }
2497
2498 fn similar_blob_objects() -> (EncodedObject, EncodedObject) {
2499 let mut base = Vec::new();
2500 for _ in 0..300 {
2501 base.extend_from_slice(b"common payload\n");
2502 }
2503 base.extend_from_slice(b"base\n");
2504 let mut changed = Vec::new();
2505 for _ in 0..300 {
2506 changed.extend_from_slice(b"common payload\n");
2507 }
2508 changed.extend_from_slice(b"changed\n");
2509 (
2510 EncodedObject::new(ObjectType::Blob, base),
2511 EncodedObject::new(ObjectType::Blob, changed),
2512 )
2513 }
2514
2515 fn single_object_pack(format: ObjectFormat, object_type: ObjectType, body: &[u8]) -> Vec<u8> {
2516 let mut pack = Vec::new();
2517 pack.extend_from_slice(b"PACK");
2518 pack.extend_from_slice(&2u32.to_be_bytes());
2519 pack.extend_from_slice(&1u32.to_be_bytes());
2520 write_entry_header(&mut pack, object_type, body.len() as u64);
2521 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
2522 encoder
2523 .write_all(body)
2524 .expect("test operation should succeed");
2525 pack.extend_from_slice(&encoder.finish().expect("test operation should succeed"));
2526 let checksum =
2527 sley_core::digest_bytes(format, &pack).expect("test operation should succeed");
2528 pack.extend_from_slice(checksum.as_bytes());
2529 pack
2530 }
2531
2532 #[derive(Clone, Copy, Debug)]
2533 enum DeltaKind {
2534 Offset,
2535 Ref,
2536 }
2537
2538 fn two_object_delta_pack(
2539 format: ObjectFormat,
2540 base: &[u8],
2541 result: &[u8],
2542 delta_kind: DeltaKind,
2543 ) -> Vec<u8> {
2544 let mut pack = Vec::new();
2545 pack.extend_from_slice(b"PACK");
2546 pack.extend_from_slice(&2u32.to_be_bytes());
2547 pack.extend_from_slice(&2u32.to_be_bytes());
2548
2549 let base_offset = pack.len();
2550 write_entry_header(&mut pack, ObjectType::Blob, base.len() as u64);
2551 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
2552 encoder
2553 .write_all(base)
2554 .expect("test operation should succeed");
2555 pack.extend_from_slice(&encoder.finish().expect("test operation should succeed"));
2556
2557 let delta = append_suffix_delta(base, result);
2558 let delta_offset = pack.len();
2559 write_pack_entry_header_kind(
2560 &mut pack,
2561 match delta_kind {
2562 DeltaKind::Offset => 6,
2563 DeltaKind::Ref => 7,
2564 },
2565 delta.len() as u64,
2566 );
2567 match delta_kind {
2568 DeltaKind::Offset => write_ofs_delta_offset(&mut pack, delta_offset - base_offset),
2569 DeltaKind::Ref => {
2570 let base_oid = sley_core::object_id_for_bytes(format, "blob", base)
2571 .expect("test operation should succeed");
2572 pack.extend_from_slice(base_oid.as_bytes());
2573 }
2574 }
2575 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
2576 encoder
2577 .write_all(&delta)
2578 .expect("test operation should succeed");
2579 pack.extend_from_slice(&encoder.finish().expect("test operation should succeed"));
2580
2581 let checksum =
2582 sley_core::digest_bytes(format, &pack).expect("test operation should succeed");
2583 pack.extend_from_slice(checksum.as_bytes());
2584 pack
2585 }
2586
2587 fn thin_ref_delta_pack(format: ObjectFormat, base: &[u8], result: &[u8]) -> Vec<u8> {
2588 let mut pack = Vec::new();
2589 pack.extend_from_slice(b"PACK");
2590 pack.extend_from_slice(&2u32.to_be_bytes());
2591 pack.extend_from_slice(&1u32.to_be_bytes());
2592
2593 let delta = append_suffix_delta(base, result);
2594 write_pack_entry_header_kind(&mut pack, 7, delta.len() as u64);
2595 let base_oid = sley_core::object_id_for_bytes(format, "blob", base)
2596 .expect("test operation should succeed");
2597 pack.extend_from_slice(base_oid.as_bytes());
2598 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
2599 encoder
2600 .write_all(&delta)
2601 .expect("test operation should succeed");
2602 pack.extend_from_slice(&encoder.finish().expect("test operation should succeed"));
2603
2604 let checksum =
2605 sley_core::digest_bytes(format, &pack).expect("test operation should succeed");
2606 pack.extend_from_slice(checksum.as_bytes());
2607 pack
2608 }
2609
2610 fn unique_temp_dir(name: &str) -> PathBuf {
2611 let nanos = SystemTime::now()
2612 .duration_since(UNIX_EPOCH)
2613 .expect("test operation should succeed")
2614 .as_nanos();
2615 std::env::temp_dir().join(format!("sley-{name}-{}-{nanos}", std::process::id()))
2616 }
2617
2618 fn run_git_success(cwd: &Path, args: &[&str]) {
2619 let output = Command::new("git")
2620 .current_dir(cwd)
2621 .args(args)
2622 .output()
2623 .unwrap_or_else(|err| panic!("failed to run git {args:?}: {err}"));
2624 assert!(
2625 output.status.success(),
2626 "git {args:?} failed with status {:?}\nstdout:\n{}\nstderr:\n{}",
2627 output.status.code(),
2628 String::from_utf8_lossy(&output.stdout),
2629 String::from_utf8_lossy(&output.stderr)
2630 );
2631 }
2632
2633 fn single_path_with_extension(dir: &Path, extension: &str) -> PathBuf {
2634 let mut paths = fs::read_dir(dir)
2635 .expect("test operation should succeed")
2636 .map(|entry| entry.expect("test operation should succeed").path())
2637 .filter(|path| path.extension().and_then(|ext| ext.to_str()) == Some(extension))
2638 .collect::<Vec<_>>();
2639 assert_eq!(paths.len(), 1, "expected one .{extension} file");
2640 paths.remove(0)
2641 }
2642
2643 fn pack_bitmap_index(
2644 format: ObjectFormat,
2645 object_count: u32,
2646 options: u16,
2647 pack_checksum: &ObjectId,
2648 entries: &[(u32, u8, u8, &[u64])],
2649 name_hash_cache: Option<&[u32]>,
2650 ) -> Vec<u8> {
2651 let mut out = Vec::new();
2652 out.extend_from_slice(b"BITM");
2653 out.extend_from_slice(&1u16.to_be_bytes());
2654 out.extend_from_slice(&options.to_be_bytes());
2655 out.extend_from_slice(&(entries.len() as u32).to_be_bytes());
2656 out.extend_from_slice(pack_checksum.as_bytes());
2657 write_test_ewah(&mut out, object_count, &[0b001]);
2658 write_test_ewah(&mut out, object_count, &[0b010]);
2659 write_test_ewah(&mut out, object_count, &[0b100]);
2660 write_test_ewah(&mut out, object_count, &[0]);
2661 for (position, xor_offset, flags, words) in entries {
2662 out.extend_from_slice(&position.to_be_bytes());
2663 out.push(*xor_offset);
2664 out.push(*flags);
2665 write_test_ewah(&mut out, object_count, words);
2666 }
2667 if let Some(cache) = name_hash_cache {
2668 for value in cache {
2669 out.extend_from_slice(&value.to_be_bytes());
2670 }
2671 }
2672 let checksum =
2673 sley_core::digest_bytes(format, &out).expect("test operation should succeed");
2674 out.extend_from_slice(checksum.as_bytes());
2675 out
2676 }
2677
2678 fn write_test_ewah(out: &mut Vec<u8>, bit_size: u32, literals: &[u64]) {
2679 out.extend_from_slice(&bit_size.to_be_bytes());
2680 let words = ewah_literal_words(literals);
2681 out.extend_from_slice(&(words.len() as u32).to_be_bytes());
2682 for word in words {
2683 out.extend_from_slice(&word.to_be_bytes());
2684 }
2685 out.extend_from_slice(&0u32.to_be_bytes());
2686 }
2687
2688 fn ewah_literal_words(literals: &[u64]) -> Vec<u64> {
2689 let rlw = (literals.len() as u64) << 33;
2690 let mut words = vec![rlw];
2691 words.extend_from_slice(literals);
2692 words
2693 }
2694
2695 fn refresh_trailing_checksum(format: ObjectFormat, bytes: &mut [u8]) {
2696 let checksum_offset = bytes.len() - format.raw_len();
2697 let checksum = sley_core::digest_bytes(format, &bytes[..checksum_offset])
2698 .expect("test operation should succeed");
2699 bytes[checksum_offset..].copy_from_slice(checksum.as_bytes());
2700 }
2701
2702 fn append_suffix_delta(base: &[u8], result: &[u8]) -> Vec<u8> {
2703 assert!(result.starts_with(base));
2704 let suffix = &result[base.len()..];
2705 assert!(base.len() < 0x10000);
2706 assert!(suffix.len() < 0x80);
2707 let mut delta = Vec::new();
2708 write_delta_varint(&mut delta, base.len() as u64);
2709 write_delta_varint(&mut delta, result.len() as u64);
2710 delta.push(0x90);
2711 delta.push(base.len() as u8);
2712 delta.push(suffix.len() as u8);
2713 delta.extend_from_slice(suffix);
2714 delta
2715 }
2716
2717 fn write_delta_varint(out: &mut Vec<u8>, mut value: u64) {
2718 loop {
2719 let mut byte = (value as u8) & 0x7f;
2720 value >>= 7;
2721 if value != 0 {
2722 byte |= 0x80;
2723 }
2724 out.push(byte);
2725 if value == 0 {
2726 break;
2727 }
2728 }
2729 }
2730
2731 fn write_pack_entry_header_kind(out: &mut Vec<u8>, type_code: u8, mut size: u64) {
2732 let mut byte = (type_code << 4) | ((size as u8) & 0x0f);
2733 size >>= 4;
2734 if size != 0 {
2735 byte |= 0x80;
2736 }
2737 out.push(byte);
2738 while size != 0 {
2739 let mut byte = (size as u8) & 0x7f;
2740 size >>= 7;
2741 if size != 0 {
2742 byte |= 0x80;
2743 }
2744 out.push(byte);
2745 }
2746 }
2747
2748 fn write_ofs_delta_offset(out: &mut Vec<u8>, relative: usize) {
2749 assert!(relative < 0x80);
2750 out.push(relative as u8);
2751 }
2752
2753 fn single_entry_index(
2754 format: ObjectFormat,
2755 oid: ObjectId,
2756 crc32: u32,
2757 offset: u32,
2758 pack_checksum: ObjectId,
2759 ) -> Vec<u8> {
2760 let mut index = Vec::new();
2761 index.extend_from_slice(&[0xff, b't', b'O', b'c']);
2762 index.extend_from_slice(&2u32.to_be_bytes());
2763 for idx in 0..256 {
2764 let count = if idx >= usize::from(oid.as_bytes()[0]) {
2765 1u32
2766 } else {
2767 0u32
2768 };
2769 index.extend_from_slice(&count.to_be_bytes());
2770 }
2771 index.extend_from_slice(oid.as_bytes());
2772 index.extend_from_slice(&crc32.to_be_bytes());
2773 index.extend_from_slice(&offset.to_be_bytes());
2774 index.extend_from_slice(pack_checksum.as_bytes());
2775 let checksum =
2776 sley_core::digest_bytes(format, &index).expect("test operation should succeed");
2777 index.extend_from_slice(checksum.as_bytes());
2778 index
2779 }
2780
2781 fn single_entry_index_v1(
2782 format: ObjectFormat,
2783 oid: ObjectId,
2784 offset: u32,
2785 pack_checksum: ObjectId,
2786 ) -> Vec<u8> {
2787 let mut index = Vec::new();
2788 for idx in 0..256 {
2789 let count = if idx >= usize::from(oid.as_bytes()[0]) {
2790 1u32
2791 } else {
2792 0u32
2793 };
2794 index.extend_from_slice(&count.to_be_bytes());
2795 }
2796 index.extend_from_slice(&offset.to_be_bytes());
2797 index.extend_from_slice(oid.as_bytes());
2798 index.extend_from_slice(pack_checksum.as_bytes());
2799 let checksum =
2800 sley_core::digest_bytes(format, &index).expect("test operation should succeed");
2801 index.extend_from_slice(checksum.as_bytes());
2802 index
2803 }
2804
2805 fn pack_reverse_index(
2806 format: ObjectFormat,
2807 positions: &[u32],
2808 pack_checksum: ObjectId,
2809 ) -> Vec<u8> {
2810 let mut reverse_index = Vec::new();
2811 reverse_index.extend_from_slice(b"RIDX");
2812 reverse_index.extend_from_slice(&1u32.to_be_bytes());
2813 reverse_index.extend_from_slice(&hash_function_id(format).to_be_bytes());
2814 for position in positions {
2815 reverse_index.extend_from_slice(&position.to_be_bytes());
2816 }
2817 reverse_index.extend_from_slice(pack_checksum.as_bytes());
2818 let checksum =
2819 sley_core::digest_bytes(format, &reverse_index).expect("test operation should succeed");
2820 reverse_index.extend_from_slice(checksum.as_bytes());
2821 reverse_index
2822 }
2823
2824 fn pack_mtimes(format: ObjectFormat, mtimes: &[u32], pack_checksum: ObjectId) -> Vec<u8> {
2825 let mut out = Vec::new();
2826 out.extend_from_slice(b"MTME");
2827 out.extend_from_slice(&1u32.to_be_bytes());
2828 out.extend_from_slice(&hash_function_id(format).to_be_bytes());
2829 for mtime in mtimes {
2830 out.extend_from_slice(&mtime.to_be_bytes());
2831 }
2832 out.extend_from_slice(pack_checksum.as_bytes());
2833 let checksum =
2834 sley_core::digest_bytes(format, &out).expect("test operation should succeed");
2835 out.extend_from_slice(checksum.as_bytes());
2836 out
2837 }
2838
2839 fn midx_chunks_with_pack_names(
2840 _format: ObjectFormat,
2841 pack_names: Vec<u8>,
2842 entries: &[(ObjectId, u32, u64)],
2843 ) -> Vec<([u8; 4], Vec<u8>)> {
2844 let mut entries = entries.to_vec();
2845 entries.sort_by(|left, right| left.0.as_bytes().cmp(right.0.as_bytes()));
2846 let object_ids: Vec<ObjectId> = entries.iter().map(|entry| entry.0).collect();
2847 let mut large_offsets = Vec::new();
2848 let mut chunks = vec![
2849 (*b"PNAM", pack_names),
2850 (*b"OIDF", midx_oid_fanout(&object_ids)),
2851 (*b"OIDL", midx_oid_lookup(&object_ids)),
2852 (
2853 *b"OOFF",
2854 midx_ooff_entries(
2855 &entries
2856 .iter()
2857 .map(|(_oid, pack_int_id, offset)| (*pack_int_id, *offset))
2858 .collect::<Vec<_>>(),
2859 &mut large_offsets,
2860 ),
2861 ),
2862 ];
2863 if !large_offsets.is_empty() {
2864 chunks.push((*b"LOFF", large_offsets));
2865 }
2866 chunks
2867 }
2868
2869 fn midx_oid_fanout(object_ids: &[ObjectId]) -> Vec<u8> {
2870 let mut counts = [0u32; 256];
2871 for oid in object_ids {
2872 counts[oid.as_bytes()[0] as usize] += 1;
2873 }
2874 let mut running = 0u32;
2875 let mut out = Vec::new();
2876 for count in counts {
2877 running += count;
2878 out.extend_from_slice(&running.to_be_bytes());
2879 }
2880 out
2881 }
2882
2883 fn midx_oid_lookup(object_ids: &[ObjectId]) -> Vec<u8> {
2884 let mut out = Vec::new();
2885 for oid in object_ids {
2886 out.extend_from_slice(oid.as_bytes());
2887 }
2888 out
2889 }
2890
2891 fn midx_ooff_entries(entries: &[(u32, u64)], large_offsets: &mut Vec<u8>) -> Vec<u8> {
2892 let mut out = Vec::new();
2893 for (pack_int_id, offset) in entries {
2894 out.extend_from_slice(&pack_int_id.to_be_bytes());
2895 if *offset < 0x8000_0000 {
2896 out.extend_from_slice(&(*offset as u32).to_be_bytes());
2897 } else {
2898 let large_idx = (large_offsets.len() / 8) as u32;
2899 out.extend_from_slice(&(0x8000_0000 | large_idx).to_be_bytes());
2900 large_offsets.extend_from_slice(&offset.to_be_bytes());
2901 }
2902 }
2903 out
2904 }
2905
2906 fn midx_u32_table(values: &[u32]) -> Vec<u8> {
2907 let mut out = Vec::new();
2908 for value in values {
2909 out.extend_from_slice(&value.to_be_bytes());
2910 }
2911 out
2912 }
2913
2914 fn midx_bitmap_packs(entries: &[(u32, u32)]) -> Vec<u8> {
2915 let mut out = Vec::new();
2916 for (bitmap_pos, bitmap_nr) in entries {
2917 out.extend_from_slice(&bitmap_pos.to_be_bytes());
2918 out.extend_from_slice(&bitmap_nr.to_be_bytes());
2919 }
2920 out
2921 }
2922
2923 fn multi_pack_index(
2924 format: ObjectFormat,
2925 version: u8,
2926 pack_count: u32,
2927 chunks: &[([u8; 4], Vec<u8>)],
2928 ) -> Vec<u8> {
2929 let lookup_len = (chunks.len() + 1) * 12;
2930 let mut out = Vec::new();
2931 out.extend_from_slice(b"MIDX");
2932 out.push(version);
2933 out.push(hash_function_id(format) as u8);
2934 out.push(chunks.len() as u8);
2935 out.push(0);
2936 out.extend_from_slice(&pack_count.to_be_bytes());
2937 let mut chunk_offset = (12 + lookup_len) as u64;
2938 for (id, data) in chunks {
2939 out.extend_from_slice(id);
2940 out.extend_from_slice(&chunk_offset.to_be_bytes());
2941 chunk_offset += data.len() as u64;
2942 }
2943 out.extend_from_slice(&[0, 0, 0, 0]);
2944 out.extend_from_slice(&chunk_offset.to_be_bytes());
2945 for (_id, data) in chunks {
2946 out.extend_from_slice(data);
2947 }
2948 let checksum =
2949 sley_core::digest_bytes(format, &out).expect("test operation should succeed");
2950 out.extend_from_slice(checksum.as_bytes());
2951 out
2952 }
2953
2954 fn pack_checksum_sha1() -> ObjectId {
2957 sley_core::digest_bytes(ObjectFormat::Sha1, b"pack").expect("test operation should succeed")
2958 }
2959
2960 fn parse_ewah_bytes(bytes: &[u8]) -> EwahBitmap {
2961 let mut offset = 0usize;
2964 let checksum_offset = bytes.len();
2965 parse_bitmap_ewah(bytes, &mut offset, checksum_offset, 0)
2966 .expect("test operation should succeed")
2967 }
2968
2969 #[test]
2970 fn ewah_encodes_single_literal_word_matching_helper() {
2971 let ewah = EwahBitmap::from_words(64, &[0b101]).expect("test operation should succeed");
2975 assert_eq!(ewah.words, ewah_literal_words(&[0b101]));
2976 assert_eq!(ewah.rlw_position, 0);
2977 assert_eq!(ewah.bit_size, 64);
2978 }
2979
2980 #[test]
2981 fn ewah_byte_layout_is_big_endian() {
2982 let ewah = EwahBitmap::from_words(64, &[0x0102_0304_0506_0708])
2983 .expect("test operation should succeed");
2984 let bytes = ewah.to_bytes();
2985 let mut expected = Vec::new();
2986 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());
2990 expected.extend_from_slice(&0u32.to_be_bytes()); assert_eq!(bytes, expected);
2992 }
2993
2994 #[test]
2995 fn ewah_empty_bitmap_serialises_like_git() {
2996 let ewah = EwahBitmap::empty();
2997 let bytes = ewah.to_bytes();
2998 assert_eq!(bytes, vec![0u8; 12]);
3000 let parsed = parse_ewah_bytes(&bytes);
3002 assert_eq!(parsed, ewah);
3003 assert!(
3004 parsed
3005 .to_positions()
3006 .expect("test operation should succeed")
3007 .is_empty()
3008 );
3009 }
3010
3011 #[test]
3012 fn ewah_compresses_clean_zero_run() {
3013 let ewah =
3016 EwahBitmap::from_words(256, &[0, 0, 0, 0b1]).expect("test operation should succeed");
3017 assert_eq!(ewah.words.len(), 2, "expected one RLW plus one literal");
3018 let rlw = ewah.words[0];
3019 assert_eq!(rlw & 1, 0, "run bit should be zero");
3020 assert_eq!((rlw >> 1) & 0xffff_ffff, 3, "run length should be 3");
3021 assert_eq!(rlw >> 33, 1, "literal length should be 1");
3022 assert_eq!(ewah.words[1], 0b1);
3023 }
3024
3025 #[test]
3026 fn ewah_compresses_clean_ones_run() {
3027 let ewah = EwahBitmap::from_words(192, &[u64::MAX, u64::MAX, u64::MAX])
3028 .expect("test operation should succeed");
3029 assert_eq!(ewah.words.len(), 1);
3031 let rlw = ewah.words[0];
3032 assert_eq!(rlw & 1, 1, "run bit should be one");
3033 assert_eq!((rlw >> 1) & 0xffff_ffff, 3, "run length should be 3");
3034 assert_eq!(rlw >> 33, 0, "no literals");
3035 }
3036
3037 #[test]
3038 fn ewah_run_then_literal_then_run_roundtrips() {
3039 let words = vec![0, 0, 0xdead_beef, u64::MAX, u64::MAX, 0, 0xabc];
3040 let bit_size = (words.len() * 64) as u32;
3041 let ewah = EwahBitmap::from_words(bit_size, &words).expect("test operation should succeed");
3042 assert_eq!(
3043 ewah.to_words().expect("test operation should succeed"),
3044 words
3045 );
3046 }
3047
3048 #[test]
3049 fn ewah_drops_trailing_clean_zero_words() {
3050 let words = vec![0b1, 0, 0, 0];
3053 let ewah = EwahBitmap::from_words(1, &words).expect("test operation should succeed");
3054 assert_eq!(ewah.bit_size, 1);
3056 assert_eq!(
3057 ewah.to_words().expect("test operation should succeed"),
3058 vec![0b1]
3059 );
3060 }
3061
3062 #[test]
3063 fn ewah_from_positions_roundtrips_via_positions() {
3064 let positions = [0u32, 1, 63, 64, 65, 200, 511];
3065 let ewah =
3066 EwahBitmap::from_positions(512, &positions).expect("test operation should succeed");
3067 let mut decoded = ewah.to_positions().expect("test operation should succeed");
3068 decoded.sort_unstable();
3069 assert_eq!(decoded, positions);
3070 }
3071
3072 #[test]
3073 fn ewah_from_positions_dedupes_and_orders() {
3074 let ewah = EwahBitmap::from_positions(128, &[100, 5, 100, 5, 5])
3075 .expect("test operation should succeed");
3076 assert_eq!(
3077 ewah.to_positions().expect("test operation should succeed"),
3078 vec![5, 100]
3079 );
3080 }
3081
3082 #[test]
3083 fn ewah_huge_zero_run_spans_multiple_rlws() {
3084 let mut builder = EwahBuilder::new(0);
3089 builder.add_empty_words(false, 0xffff_ffff);
3090 builder.add_empty_words(false, 5);
3091 let ewah = builder.finish().expect("test operation should succeed");
3092 assert_eq!(ewah.words.len(), 2, "run split across two RLWs");
3093 assert_eq!((ewah.words[0] >> 1) & 0xffff_ffff, 0xffff_ffff);
3094 assert_eq!(ewah.words[1] & 1, 0);
3095 assert_eq!((ewah.words[1] >> 1) & 0xffff_ffff, 5);
3096 assert_eq!(ewah.rlw_position, 1);
3097 }
3098
3099 #[test]
3100 fn ewah_from_words_rejects_oversized_bit_size() {
3101 assert!(EwahBitmap::from_words(65, &[0]).is_err());
3103 }
3104
3105 #[test]
3106 fn ewah_from_positions_rejects_out_of_range() {
3107 assert!(EwahBitmap::from_positions(64, &[64]).is_err());
3108 }
3109
3110 #[test]
3111 fn ewah_serialised_bytes_reparse_to_equal_bitmap() {
3112 let words = vec![0, u64::MAX, 0x1234_5678_9abc_def0, 0, 0, 0xff];
3115 let bit_size = (words.len() * 64) as u32;
3116 let ewah = EwahBitmap::from_words(bit_size, &words).expect("test operation should succeed");
3117 let bytes = ewah.to_bytes();
3118 let parsed = parse_ewah_bytes(&bytes);
3119 assert_eq!(parsed, ewah);
3120 assert_eq!(
3121 parsed.to_words().expect("test operation should succeed"),
3122 words
3123 );
3124 }
3125
3126 #[test]
3127 fn pack_bitmap_index_write_parse_roundtrip_sha1() {
3128 let object_types = [ObjectType::Commit, ObjectType::Tree, ObjectType::Blob];
3130 let bytes = write_bitmap(
3131 ObjectFormat::Sha1,
3132 pack_checksum_sha1(),
3133 &object_types,
3134 &[(0u32, 0u32, vec![1u32, 2u32])],
3135 None,
3136 )
3137 .expect("test operation should succeed");
3138 assert_eq!(&bytes[..4], b"BITM");
3139
3140 let parsed = PackBitmapIndex::parse(&bytes, ObjectFormat::Sha1, 3)
3141 .expect("test operation should succeed");
3142 assert_eq!(parsed.version, 1);
3143 assert_eq!(parsed.options, PackBitmapIndex::OPTION_FULL_DAG);
3144 assert_eq!(parsed.pack_checksum, pack_checksum_sha1());
3145 assert_eq!(
3146 parsed
3147 .type_bitmaps
3148 .commits
3149 .to_positions()
3150 .expect("test operation should succeed"),
3151 vec![0]
3152 );
3153 assert_eq!(
3154 parsed
3155 .type_bitmaps
3156 .trees
3157 .to_positions()
3158 .expect("test operation should succeed"),
3159 vec![1]
3160 );
3161 assert_eq!(
3162 parsed
3163 .type_bitmaps
3164 .blobs
3165 .to_positions()
3166 .expect("test operation should succeed"),
3167 vec![2]
3168 );
3169 assert!(
3170 parsed
3171 .type_bitmaps
3172 .tags
3173 .to_positions()
3174 .expect("test operation should succeed")
3175 .is_empty()
3176 );
3177 assert_eq!(parsed.entries.len(), 1);
3178 let entry = parsed
3179 .entry_for_index_position(0)
3180 .expect("test operation should succeed");
3181 assert_eq!(entry.xor_offset, 0);
3182 assert_eq!(entry.flags, 0);
3183 assert_eq!(
3184 entry
3185 .bitmap
3186 .to_positions()
3187 .expect("test operation should succeed"),
3188 vec![0, 1, 2]
3189 );
3190 assert_eq!(parsed.name_hash_cache, None);
3191 }
3192
3193 #[test]
3194 fn pack_bitmap_index_write_parse_roundtrip_sha256() {
3195 let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha256, b"pack")
3196 .expect("test operation should succeed");
3197 let object_types = [ObjectType::Commit, ObjectType::Tree];
3198 let bytes = write_bitmap(
3199 ObjectFormat::Sha256,
3200 pack_checksum.clone(),
3201 &object_types,
3202 &[(0u32, 0u32, vec![1u32])],
3203 None,
3204 )
3205 .expect("test operation should succeed");
3206 let parsed = PackBitmapIndex::parse(&bytes, ObjectFormat::Sha256, 2)
3207 .expect("test operation should succeed");
3208 assert_eq!(parsed.format, ObjectFormat::Sha256);
3209 assert_eq!(parsed.pack_checksum, pack_checksum);
3210 assert_eq!(parsed.index_checksum.format(), ObjectFormat::Sha256);
3211 assert_eq!(
3212 parsed.entries[0]
3213 .bitmap
3214 .to_positions()
3215 .expect("test operation should succeed"),
3216 vec![0, 1]
3217 );
3218 }
3219
3220 #[test]
3221 fn pack_bitmap_index_write_includes_name_hash_cache() {
3222 let object_types = [ObjectType::Commit, ObjectType::Tree, ObjectType::Blob];
3223 let cache = vec![0x1111_1111u32, 0x2222_2222, 0x3333_3333];
3224 let bytes = write_bitmap(
3225 ObjectFormat::Sha1,
3226 pack_checksum_sha1(),
3227 &object_types,
3228 &[(0u32, 0u32, vec![1u32, 2u32])],
3229 Some(cache.clone()),
3230 )
3231 .expect("test operation should succeed");
3232 let parsed = PackBitmapIndex::parse(&bytes, ObjectFormat::Sha1, 3)
3233 .expect("test operation should succeed");
3234 assert_eq!(
3235 parsed.options,
3236 PackBitmapIndex::OPTION_FULL_DAG | PackBitmapIndex::OPTION_HASH_CACHE
3237 );
3238 assert_eq!(parsed.name_hash_cache, Some(cache));
3239 }
3240
3241 #[test]
3242 fn pack_bitmap_writer_supports_multiple_commits() {
3243 let object_types = [
3244 ObjectType::Commit,
3245 ObjectType::Commit,
3246 ObjectType::Tree,
3247 ObjectType::Blob,
3248 ];
3249 let mut writer =
3250 PackBitmapWriter::new(ObjectFormat::Sha1, pack_checksum_sha1(), &object_types)
3251 .expect("test operation should succeed");
3252 writer
3253 .add_commit(0, 0, &[2, 3])
3254 .expect("test operation should succeed");
3255 writer
3256 .add_commit(1, 1, &[2])
3257 .expect("test operation should succeed");
3258 let bytes = writer.write().expect("test operation should succeed");
3259 let parsed = PackBitmapIndex::parse(&bytes, ObjectFormat::Sha1, 4)
3260 .expect("test operation should succeed");
3261 assert_eq!(parsed.entries.len(), 2);
3262 assert_eq!(
3263 parsed
3264 .type_bitmaps
3265 .commits
3266 .to_positions()
3267 .expect("test operation should succeed"),
3268 vec![0, 1]
3269 );
3270 let first = parsed
3271 .entry_for_index_position(0)
3272 .expect("test operation should succeed");
3273 assert_eq!(
3274 first
3275 .bitmap
3276 .to_positions()
3277 .expect("test operation should succeed"),
3278 vec![0, 2, 3]
3279 );
3280 let second = parsed
3281 .entry_for_index_position(1)
3282 .expect("test operation should succeed");
3283 assert_eq!(
3284 second
3285 .bitmap
3286 .to_positions()
3287 .expect("test operation should succeed"),
3288 vec![1, 2]
3289 );
3290 }
3291
3292 #[test]
3293 fn pack_bitmap_index_recomputes_checksum_on_write() {
3294 let object_types = [ObjectType::Commit, ObjectType::Blob];
3297 let writer = PackBitmapWriter::new(ObjectFormat::Sha1, pack_checksum_sha1(), &object_types)
3298 .expect("test operation should succeed");
3299 let mut index = writer.build().expect("test operation should succeed");
3300 assert_eq!(index.index_checksum.as_bytes(), [0u8; 20]);
3302 index.entries.clear(); index.entries.push(PackBitmapEntry {
3304 object_position: 0,
3305 xor_offset: 0,
3306 flags: 0,
3307 bitmap: EwahBitmap::from_positions(2, &[0, 1]).expect("test operation should succeed"),
3308 });
3309 let bytes = index.write().expect("test operation should succeed");
3310 let parsed = PackBitmapIndex::parse(&bytes, ObjectFormat::Sha1, 2)
3312 .expect("test operation should succeed");
3313 assert_ne!(parsed.index_checksum.as_bytes(), [0u8; 20]);
3314 }
3315
3316 #[test]
3317 fn pack_bitmap_writer_rejects_non_commit_selection() {
3318 let object_types = [ObjectType::Commit, ObjectType::Blob];
3319 let mut writer =
3320 PackBitmapWriter::new(ObjectFormat::Sha1, pack_checksum_sha1(), &object_types)
3321 .expect("test operation should succeed");
3322 assert!(writer.add_commit(1, 1, &[]).is_err());
3324 assert!(writer.add_commit(5, 5, &[]).is_err());
3326 assert!(writer.add_commit(0, 5, &[]).is_err());
3328 assert!(writer.add_commit(0, 0, &[9]).is_err());
3330 }
3331
3332 #[test]
3333 fn pack_bitmap_writer_rejects_checksum_format_mismatch() {
3334 let sha256_checksum = sley_core::digest_bytes(ObjectFormat::Sha256, b"pack")
3335 .expect("test operation should succeed");
3336 assert!(
3337 PackBitmapWriter::new(ObjectFormat::Sha1, sha256_checksum, &[ObjectType::Commit])
3338 .is_err()
3339 );
3340 }
3341
3342 #[test]
3343 fn pack_bitmap_writer_rejects_bad_name_hash_cache_len() {
3344 let writer = PackBitmapWriter::new(
3345 ObjectFormat::Sha1,
3346 pack_checksum_sha1(),
3347 &[ObjectType::Commit],
3348 )
3349 .expect("test operation should succeed");
3350 assert!(writer.with_name_hash_cache(vec![1, 2]).is_err());
3351 }
3352
3353 #[test]
3354 fn pack_bitmap_index_write_rejects_inconsistent_cache_flag() {
3355 let mut index = PackBitmapWriter::new(
3356 ObjectFormat::Sha1,
3357 pack_checksum_sha1(),
3358 &[ObjectType::Commit],
3359 )
3360 .expect("test operation should succeed")
3361 .build()
3362 .expect("test operation should succeed");
3363 index.options |= PackBitmapIndex::OPTION_HASH_CACHE;
3365 assert!(index.write().is_err());
3366 index.options = PackBitmapIndex::OPTION_FULL_DAG;
3368 index.name_hash_cache = Some(vec![0]);
3369 assert!(index.write().is_err());
3370 }
3371
3372 #[test]
3373 fn write_bitmap_roundtrips_through_upstream_git_parser() {
3374 let root = unique_temp_dir("git-pack-bitmap-writer");
3378 fs::create_dir_all(&root).expect("test operation should succeed");
3379 {
3380 run_git_success(&root, &["init", "-q", "-b", "main"]);
3381 run_git_success(
3382 &root,
3383 &[
3384 "-c",
3385 "user.name=Example User",
3386 "-c",
3387 "user.email=example@example.invalid",
3388 "commit",
3389 "--allow-empty",
3390 "-q",
3391 "-m",
3392 "one",
3393 ],
3394 );
3395 run_git_success(&root, &["repack", "-adb"]);
3396 let pack_dir = root.join(".git").join("objects").join("pack");
3397 let idx_path = single_path_with_extension(&pack_dir, "idx");
3398 let index = PackIndex::parse(
3399 &fs::read(idx_path).expect("test operation should succeed"),
3400 ObjectFormat::Sha1,
3401 )
3402 .expect("test operation should succeed");
3403 let pack_path = single_path_with_extension(&pack_dir, "pack");
3405 let pack =
3406 PackFile::parse_sha1(&fs::read(pack_path).expect("test operation should succeed"))
3407 .expect("test operation should succeed");
3408 let mut offsets: Vec<u64> = index.entries.iter().map(|entry| entry.offset).collect();
3411 offsets.sort_unstable();
3412 let position_of = |offset: u64| -> u32 {
3413 offsets
3414 .iter()
3415 .position(|value| *value == offset)
3416 .expect("test operation should succeed") as u32
3417 };
3418 let mut object_types = vec![ObjectType::Blob; index.entries.len()];
3419 for entry in &index.entries {
3420 let position = position_of(entry.offset) as usize;
3421 if let Some(parsed) = pack
3423 .entries
3424 .iter()
3425 .find(|po| po.entry.offset == entry.offset)
3426 {
3427 object_types[position] = parsed.object.object_type;
3428 }
3429 }
3430 let commit_position = object_types
3432 .iter()
3433 .position(|ty| *ty == ObjectType::Commit)
3434 .expect("test operation should succeed") as u32;
3435 let commit_index_position = index
3437 .entries
3438 .iter()
3439 .position(|entry| position_of(entry.offset) == commit_position)
3440 .expect("test operation should succeed")
3441 as u32;
3442 let reachable: Vec<u32> = (0..index.entries.len() as u32).collect();
3443 let bytes = write_bitmap(
3444 ObjectFormat::Sha1,
3445 index.pack_checksum.clone(),
3446 &object_types,
3447 &[(commit_position, commit_index_position, reachable)],
3448 None,
3449 )
3450 .expect("test operation should succeed");
3451 let parsed = PackBitmapIndex::parse(&bytes, ObjectFormat::Sha1, index.entries.len())
3452 .expect("test operation should succeed");
3453 assert_eq!(parsed.pack_checksum, index.pack_checksum);
3454 assert_eq!(parsed.entries.len(), 1);
3455 assert_eq!(
3456 parsed.entries[0]
3457 .bitmap
3458 .to_positions()
3459 .expect("test operation should succeed")
3460 .len(),
3461 index.entries.len()
3462 );
3463 };
3464 let _ = fs::remove_dir_all(&root);
3465 }
3466}