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